summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt25
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt (renamed from src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt)44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt101
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt80
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt5
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.cpp16
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.h7
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp101
-rw-r--r--src/android/app/src/main/jni/id_cache.h17
-rw-r--r--src/android/app/src/main/jni/native.cpp128
-rw-r--r--src/android/app/src/main/jni/native.h2
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml6
-rw-r--r--src/android/app/src/main/res/layout/dialog_progress_bar.xml30
-rw-r--r--src/android/app/src/main/res/layout/list_item_addon.xml32
-rw-r--r--src/android/app/src/main/res/values/strings.xml3
-rw-r--r--src/core/file_sys/patch_manager.cpp43
-rw-r--r--src/core/file_sys/patch_manager.h17
-rw-r--r--src/core/hle/service/hid/hid.cpp1
-rw-r--r--src/core/hle/service/hid/hid_server.cpp334
-rw-r--r--src/core/hle/service/hid/hid_server.h1
-rw-r--r--src/core/hle/service/hid/hid_system_server.cpp98
-rw-r--r--src/core/hle/service/hid/hid_system_server.h6
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp16
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h4
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp11
-rw-r--r--src/core/hle/service/vi/display/vi_display.h3
-rw-r--r--src/core/hle/service/vi/layer/vi_layer.h9
-rw-r--r--src/core/hle/service/vi/vi.cpp14
-rw-r--r--src/frontend_common/CMakeLists.txt1
-rw-r--r--src/frontend_common/content_manager.h238
-rw-r--r--src/hid_core/frontend/emulated_controller.cpp40
-rw-r--r--src/hid_core/frontend/emulated_controller.h20
-rw-r--r--src/hid_core/resource_manager.cpp123
-rw-r--r--src/hid_core/resource_manager.h25
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp2
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h6
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad.cpp13
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad.h6
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp42
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h12
-rw-r--r--src/hid_core/resources/applet_resource.cpp19
-rw-r--r--src/hid_core/resources/applet_resource.h1
-rw-r--r--src/hid_core/resources/npad/npad.cpp292
-rw-r--r--src/hid_core/resources/npad/npad.h59
-rw-r--r--src/hid_core/resources/npad/npad_types.h6
-rw-r--r--src/hid_core/resources/npad/npad_vibration.cpp4
-rw-r--r--src/hid_core/resources/npad/npad_vibration.h2
-rw-r--r--src/hid_core/resources/vibration/gc_vibration_device.cpp56
-rw-r--r--src/hid_core/resources/vibration/gc_vibration_device.h10
-rw-r--r--src/hid_core/resources/vibration/n64_vibration_device.cpp57
-rw-r--r--src/hid_core/resources/vibration/n64_vibration_device.h8
-rw-r--r--src/hid_core/resources/vibration/vibration_base.cpp4
-rw-r--r--src/hid_core/resources/vibration/vibration_base.h10
-rw-r--r--src/hid_core/resources/vibration/vibration_device.cpp72
-rw-r--r--src/hid_core/resources/vibration/vibration_device.h16
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp7
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp8
-rw-r--r--src/yuzu/game_list_worker.cpp11
-rw-r--r--src/yuzu/main.cpp166
-rw-r--r--src/yuzu/main.h11
72 files changed, 1864 insertions, 892 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
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
+import org.yuzu.yuzu_emu.model.InstallResult
+import org.yuzu.yuzu_emu.model.Patch
/**
* Class which contains methods that interact
@@ -235,9 +237,12 @@ object NativeLibrary {
/**
* Installs a nsp or xci file to nand
* @param filename String representation of file uri
- * @param extension Lowercase string representation of file extension without "."
+ * @return int representation of [InstallResult]
*/
- external fun installFileToNand(filename: String, extension: String): Int
+ external fun installFileToNand(
+ filename: String,
+ callback: (max: Long, progress: Long) -> Boolean
+ ): Int
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
@@ -535,9 +540,29 @@ object NativeLibrary {
*
* @param path Path to game file. Can be a [Uri].
* @param programId String representation of a game's program ID
- * @return Array of pairs where the first value is the name of an addon and the second is the version
+ * @return Array of available patches
*/
- external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>?
+ external fun getPatchesForFile(path: String, programId: String): Array<Patch>?
+
+ /**
+ * Removes an update for a given [programId]
+ * @param programId String representation of a game's program ID
+ */
+ external fun removeUpdate(programId: String)
+
+ /**
+ * Removes all DLC for a [programId]
+ * @param programId String representation of a game's program ID
+ */
+ external fun removeDLC(programId: String)
+
+ /**
+ * Removes a mod installed for a given [programId]
+ * @param programId String representation of a game's program ID
+ * @param name The name of a mod as given by [getPatchesForFile]. This corresponds with the name
+ * of the mod's directory in a game's load folder.
+ */
+ external fun removeMod(programId: String, name: String)
/**
* Gets the save location for a specific game
@@ -609,15 +634,4 @@ object NativeLibrary {
const val RELEASED = 0
const val PRESSED = 1
}
-
- /**
- * Result from installFileToNand
- */
- object InstallFileToNandResult {
- const val Success = 0
- const val SuccessFileOverwritten = 1
- const val Error = 2
- const val ErrorBaseGame = 3
- const val ErrorFilenameExtension = 4
- }
}
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
import android.view.LayoutInflater
import android.view.ViewGroup
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
-import org.yuzu.yuzu_emu.model.Addon
+import org.yuzu.yuzu_emu.model.Patch
+import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() {
+class AddonAdapter(val addonViewModel: AddonViewModel) :
+ AbstractDiffAdapter<Patch, AddonAdapter.AddonViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) }
}
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
- AbstractViewHolder<Addon>(binding) {
- override fun bind(model: Addon) {
+ AbstractViewHolder<Patch>(binding) {
+ override fun bind(model: Patch) {
binding.root.setOnClickListener {
- binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
+ binding.addonCheckbox.isChecked = !binding.addonCheckbox.isChecked
}
- binding.title.text = model.title
+ binding.title.text = model.name
binding.version.text = model.version
- binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
+ binding.addonCheckbox.setOnCheckedChangeListener { _, checked ->
model.enabled = checked
}
- binding.addonSwitch.isChecked = model.enabled
+ binding.addonCheckbox.isChecked = model.enabled
+ binding.buttonDelete.setOnClickListener {
+ addonViewModel.setAddonToDelete(model)
+ }
}
}
}
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() {
binding.listAddons.apply {
layoutManager = LinearLayoutManager(requireContext())
- adapter = AddonAdapter()
+ adapter = AddonAdapter(addonViewModel)
}
viewLifecycleOwner.lifecycleScope.apply {
@@ -110,6 +110,21 @@ class AddonsFragment : Fragment() {
}
}
}
+ launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ addonViewModel.addonToDelete.collect {
+ if (it != null) {
+ MessageDialogFragment.newInstance(
+ requireActivity(),
+ titleId = R.string.confirm_uninstall,
+ descriptionId = R.string.confirm_uninstall_description,
+ positiveAction = { addonViewModel.onDeleteAddon(it) }
+ ).show(parentFragmentManager, MessageDialogFragment.TAG)
+ addonViewModel.setAddonToDelete(null)
+ }
+ }
+ }
+ }
}
binding.buttonInstall.setOnClickListener {
@@ -156,22 +171,22 @@ class AddonsFragment : Fragment() {
descriptionId = R.string.invalid_directory_description
)
if (isValid) {
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_game_content,
false
- ) {
+ ) { progressCallback, _ ->
val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName)
try {
- externalAddonDirectory.copyFilesTo(internalAddonDirectory)
+ externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback)
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully)
- }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
}
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() {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_driver,
false
- ) {
+ ) { _, _ ->
val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
val driverFile = File(driverPath)
@@ -213,6 +213,6 @@ class DriverManagerFragment : Fragment() {
}
}
return@newInstance Any()
- }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(childFragmentManager, ProgressDialogFragment.TAG)
}
}
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
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
-import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
@@ -357,27 +356,17 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
- val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
- if (inputZip == null) {
- Toast.makeText(
- YuzuApplication.appContext,
- getString(R.string.fatal_error),
- Toast.LENGTH_LONG
- ).show()
- return@registerForActivityResult
- }
-
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
- ) {
+ ) { _, _ ->
try {
- FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
+ FileUtil.unzipToInternalStorage(result.toString(), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null
if (files != null) {
@@ -422,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
- }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
/**
@@ -436,11 +425,11 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
- ) {
+ ) { _, _ ->
val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation),
@@ -452,6 +441,6 @@ class GamePropertiesFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
- }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}
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
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
-import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.math.BigInteger
@@ -195,26 +194,20 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
- val inputZip = requireContext().contentResolver.openInputStream(result)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
- if (inputZip == null) {
- Toast.makeText(
- YuzuApplication.appContext,
- getString(R.string.fatal_error),
- Toast.LENGTH_LONG
- ).show()
- return@registerForActivityResult
- }
-
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
- ) {
+ ) { progressCallback, _ ->
try {
- FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
+ FileUtil.unzipToInternalStorage(
+ result.toString(),
+ cacheSaveDir,
+ progressCallback
+ )
val files = cacheSaveDir.listFiles()
var successfulImports = 0
var failedImports = 0
@@ -287,7 +280,7 @@ class InstallableFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
- }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
private val exportSaves = registerForActivityResult(
@@ -297,11 +290,11 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
- ) {
+ ) { _, _ ->
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
@@ -338,6 +331,6 @@ class InstallableFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
- }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}
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
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
-class IndeterminateProgressDialogFragment : DialogFragment() {
+class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
private lateinit var binding: DialogProgressBarBinding
+ private val PROGRESS_BAR_RESOLUTION = 1000
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE)
@@ -61,6 +63,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ binding.message.isSelected = true
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -97,6 +100,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
}
}
}
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ taskViewModel.progress.collect {
+ if (it != 0.0) {
+ binding.progressBar.apply {
+ isIndeterminate = false
+ progress = (
+ (it / taskViewModel.maxProgress.value) *
+ PROGRESS_BAR_RESOLUTION
+ ).toInt()
+ min = 0
+ max = PROGRESS_BAR_RESOLUTION
+ }
+ }
+ }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ taskViewModel.message.collect {
+ if (it.isEmpty()) {
+ binding.message.visibility = View.GONE
+ } else {
+ binding.message.visibility = View.VISIBLE
+ binding.message.text = it
+ }
+ }
+ }
+ }
}
}
@@ -108,6 +140,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
alertDialog.setTitle(getString(R.string.cancelling))
+ binding.progressBar.isIndeterminate = true
taskViewModel.setCancelled(true)
}
}
@@ -122,9 +155,12 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
- task: suspend () -> Any
- ): IndeterminateProgressDialogFragment {
- val dialog = IndeterminateProgressDialogFragment()
+ task: suspend (
+ progressCallback: (max: Long, progress: Long) -> Boolean,
+ messageCallback: (message: String) -> Unit
+ ) -> Any
+ ): ProgressDialogFragment {
+ val dialog = ProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
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() {
baseList.filter {
val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
- }
+ }.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) }
}
R.id.chip_recently_added -> {
baseList.filter {
val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
- }
+ }.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) }
}
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 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.model
-
-data class Addon(
- var enabled: Boolean,
- val title: String,
- val version: String
-)
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
import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() {
- private val _addonList = MutableStateFlow(mutableListOf<Addon>())
- val addonList get() = _addonList.asStateFlow()
+ private val _patchList = MutableStateFlow(mutableListOf<Patch>())
+ val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false)
val showModInstallPicker get() = _showModInstallPicker.asStateFlow()
@@ -24,6 +24,9 @@ class AddonViewModel : ViewModel() {
private val _showModNoticeDialog = MutableStateFlow(false)
val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow()
+ private val _addonToDelete = MutableStateFlow<Patch?>(null)
+ val addonToDelete = _addonToDelete.asStateFlow()
+
var game: Game? = null
private val isRefreshing = AtomicBoolean(false)
@@ -40,36 +43,47 @@ class AddonViewModel : ViewModel() {
isRefreshing.set(true)
viewModelScope.launch {
withContext(Dispatchers.IO) {
- val addonList = mutableListOf<Addon>()
- val disabledAddons = NativeConfig.getDisabledAddons(game!!.programId)
- NativeLibrary.getAddonsForFile(game!!.path, game!!.programId)?.forEach {
- val name = it.first.replace("[D] ", "")
- addonList.add(Addon(!disabledAddons.contains(name), name, it.second))
- }
- addonList.sortBy { it.title }
- _addonList.value = addonList
+ val patchList = (
+ NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
+ ?: emptyArray()
+ ).toMutableList()
+ patchList.sortBy { it.name }
+ _patchList.value = patchList
isRefreshing.set(false)
}
}
}
+ fun setAddonToDelete(patch: Patch?) {
+ _addonToDelete.value = patch
+ }
+
+ fun onDeleteAddon(patch: Patch) {
+ when (PatchType.from(patch.type)) {
+ PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
+ PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
+ PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
+ }
+ refreshAddons()
+ }
+
fun onCloseAddons() {
- if (_addonList.value.isEmpty()) {
+ if (_patchList.value.isEmpty()) {
return
}
NativeConfig.setDisabledAddons(
game!!.programId,
- _addonList.value.mapNotNull {
+ _patchList.value.mapNotNull {
if (it.enabled) {
null
} else {
- it.title
+ it.name
}
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
- _addonList.value.clear()
+ _patchList.value.clear()
game = null
}
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 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+enum class InstallResult(val int: Int) {
+ Success(0),
+ Overwrite(1),
+ Failure(2),
+ BaseInstallAttempted(3);
+
+ companion object {
+ fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success
+ }
+}
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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.annotation.Keep
+
+@Keep
+data class Patch(
+ var enabled: Boolean,
+ val name: String,
+ val version: String,
+ val type: Int,
+ val programId: String,
+ val titleId: String
+)
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 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+enum class PatchType(val int: Int) {
+ Update(0),
+ DLC(1),
+ Mod(2);
+
+ companion object {
+ fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update
+ }
+}
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
@@ -23,13 +24,28 @@ class TaskViewModel : ViewModel() {
val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false)
- lateinit var task: suspend () -> Any
+ private val _progress = MutableStateFlow(0.0)
+ val progress = _progress.asStateFlow()
+
+ private val _maxProgress = MutableStateFlow(0.0)
+ val maxProgress = _maxProgress.asStateFlow()
+
+ private val _message = MutableStateFlow("")
+ val message = _message.asStateFlow()
+
+ lateinit var task: suspend (
+ progressCallback: (max: Long, progress: Long) -> Boolean,
+ messageCallback: (message: String) -> Unit
+ ) -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
_cancelled.value = false
+ _progress.value = 0.0
+ _maxProgress.value = 0.0
+ _message.value = ""
}
fun setCancelled(value: Boolean) {
@@ -43,7 +59,16 @@ class TaskViewModel : ViewModel() {
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
- val res = task()
+ val res = task(
+ { max, progress ->
+ _maxProgress.value = max.toDouble()
+ _progress.value = progress.toDouble()
+ return@task cancelled.value
+ },
+ { message ->
+ _message.value = message
+ }
+ )
_result.value = res
_isComplete.value = true
_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
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
-import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
+import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
@@ -369,26 +370,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
- val inputZip = contentResolver.openInputStream(result)
- if (inputZip == null) {
- Toast.makeText(
- applicationContext,
- getString(R.string.fatal_error),
- Toast.LENGTH_LONG
- ).show()
- return@registerForActivityResult
- }
-
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
- val task: () -> Any = {
+ ProgressDialogFragment.newInstance(
+ this,
+ R.string.firmware_installing
+ ) { progressCallback, _ ->
var messageToShow: Any
try {
- FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
+ FileUtil.unzipToInternalStorage(
+ result.toString(),
+ cacheFirmwareDir,
+ progressCallback
+ )
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
@@ -404,18 +402,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
+ Log.error("[MainActivity] Firmware install failed - ${e.message}")
messageToShow = getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
- }
-
- IndeterminateProgressDialogFragment.newInstance(
- this,
- R.string.firmware_installing,
- task = task
- ).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val getAmiiboKey =
@@ -474,11 +467,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.verifying_content,
false
- ) {
+ ) { _, _ ->
var updatesMatchProgram = true
for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram(
@@ -501,44 +494,42 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
positiveAction = { homeViewModel.setContentToInstall(documents) }
)
}
- }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
private fun installContent(documents: List<Uri>) {
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.installing_game_content
- ) {
+ ) { progressCallback, messageCallback ->
var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
- var errorExtension = 0
- var errorOther = 0
+ var error = 0
documents.forEach {
+ messageCallback.invoke(FileUtil.getFilename(it))
when (
- NativeLibrary.installFileToNand(
- it.toString(),
- FileUtil.getExtension(it)
+ InstallResult.from(
+ NativeLibrary.installFileToNand(
+ it.toString(),
+ progressCallback
+ )
)
) {
- NativeLibrary.InstallFileToNandResult.Success -> {
+ InstallResult.Success -> {
installSuccess += 1
}
- NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
+ InstallResult.Overwrite -> {
installOverwrite += 1
}
- NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
+ InstallResult.BaseInstallAttempted -> {
errorBaseGame += 1
}
- NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- errorExtension += 1
- }
-
- else -> {
- errorOther += 1
+ InstallResult.Failure -> {
+ error += 1
}
}
}
@@ -565,7 +556,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
- val errorTotal: Int = errorBaseGame + errorExtension + errorOther
+ val errorTotal: Int = errorBaseGame + error
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
@@ -582,14 +573,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
- if (errorExtension > 0) {
- installResult.append(separator)
- installResult.append(
- getString(R.string.install_game_content_failure_file_extension)
- )
- installResult.append(separator)
- }
- if (errorOther > 0) {
+ if (error > 0) {
installResult.append(
getString(R.string.install_game_content_failure_description)
)
@@ -608,7 +592,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
descriptionString = installResult.toString().trim()
)
}
- }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val exportUserData = registerForActivityResult(
@@ -618,16 +602,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
- ) {
+ ) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!),
DirectoryInitialization.userDirectory!!,
BufferedOutputStream(contentResolver.openOutputStream(result)),
- taskViewModel.cancelled,
+ progressCallback,
compression = false
)
return@newInstance when (zipResult) {
@@ -635,7 +619,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled
}
- }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val importUserData =
@@ -644,10 +628,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
- IndeterminateProgressDialogFragment.newInstance(
+ ProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
- ) {
+ ) { progressCallback, _ ->
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
@@ -676,8 +660,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Copy archive to internal storage
try {
FileUtil.unzipToInternalStorage(
- BufferedInputStream(contentResolver.openInputStream(result)),
- File(DirectoryInitialization.userDirectory!!)
+ result.toString(),
+ File(DirectoryInitialization.userDirectory!!),
+ progressCallback
)
} catch (e: Exception) {
return@newInstance MessageDialogFragment.newInstance(
@@ -694,6 +679,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success)
- }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
}
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
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
-import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
@@ -19,6 +18,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream
+import java.io.OutputStream
import java.lang.NullPointerException
import java.nio.charset.StandardCharsets
import java.util.zip.Deflater
@@ -283,12 +283,34 @@ object FileUtil {
/**
* Extracts the given zip file into the given directory.
+ * @param path String representation of a [Uri] or a typical path delimited by '/'
+ * @param destDir Location to unzip the contents of [path] into
+ * @param progressCallback Lambda that is called with the total number of files and the current
+ * progress through the process. Stops execution as soon as possible if this returns true.
*/
@Throws(SecurityException::class)
- fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
- ZipInputStream(zipStream).use { zis ->
+ fun unzipToInternalStorage(
+ path: String,
+ destDir: File,
+ progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
+ ) {
+ var totalEntries = 0L
+ ZipInputStream(getInputStream(path)).use { zis ->
+ var tempEntry = zis.nextEntry
+ while (tempEntry != null) {
+ tempEntry = zis.nextEntry
+ totalEntries++
+ }
+ }
+
+ var progress = 0L
+ ZipInputStream(getInputStream(path)).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
+ if (progressCallback.invoke(totalEntries, progress)) {
+ return@use
+ }
+
val newFile = File(destDir, entry.name)
val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
@@ -304,6 +326,7 @@ object FileUtil {
newFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
+ progress++
}
}
}
@@ -313,14 +336,15 @@ object FileUtil {
* @param inputFile File representation of the item that will be zipped
* @param rootDir Directory containing the inputFile
* @param outputStream Stream where the zip file will be output
- * @param cancelled [StateFlow] that reports whether this process has been cancelled
+ * @param progressCallback Lambda that is called with the total number of files and the current
+ * progress through the process. Stops execution as soon as possible if this returns true.
* @param compression Disables compression if true
*/
fun zipFromInternalStorage(
inputFile: File,
rootDir: String,
outputStream: BufferedOutputStream,
- cancelled: StateFlow<Boolean>? = null,
+ progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false },
compression: Boolean = true
): TaskState {
try {
@@ -330,8 +354,10 @@ object FileUtil {
zos.setLevel(Deflater.NO_COMPRESSION)
}
+ var count = 0L
+ val totalFiles = inputFile.walkTopDown().count().toLong()
inputFile.walkTopDown().forEach { file ->
- if (cancelled?.value == true) {
+ if (progressCallback.invoke(totalFiles, count)) {
return TaskState.Cancelled
}
@@ -343,6 +369,7 @@ object FileUtil {
if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) }
}
+ count++
}
}
}
@@ -356,9 +383,14 @@ object FileUtil {
/**
* Helper function that copies the contents of a DocumentFile folder into a [File]
* @param file [File] representation of the folder to copy into
+ * @param progressCallback Lambda that is called with the total number of files and the current
+ * progress through the process. Stops execution as soon as possible if this returns true.
* @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa
*/
- fun DocumentFile.copyFilesTo(file: File) {
+ fun DocumentFile.copyFilesTo(
+ file: File,
+ progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
+ ) {
file.mkdirs()
if (!this.isDirectory || !file.isDirectory) {
throw IllegalStateException(
@@ -366,7 +398,13 @@ object FileUtil {
)
}
+ var count = 0L
+ val totalFiles = this.listFiles().size.toLong()
this.listFiles().forEach {
+ if (progressCallback.invoke(totalFiles, count)) {
+ return
+ }
+
val newFile = File(file, it.name!!)
if (it.isDirectory) {
newFile.mkdirs()
@@ -381,6 +419,7 @@ object FileUtil {
newFile.outputStream().use { os -> bos.copyTo(os) }
}
}
+ count++
}
}
@@ -427,6 +466,18 @@ object FileUtil {
}
}
+ fun getInputStream(path: String) = if (path.contains("content://")) {
+ Uri.parse(path).inputStream()
+ } else {
+ File(path).inputStream()
+ }
+
+ fun getOutputStream(path: String) = if (path.contains("content://")) {
+ Uri.parse(path).outputStream()
+ } else {
+ File(path).outputStream()
+ }
+
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8)
@@ -434,4 +485,19 @@ object FileUtil {
@Throws(IOException::class)
fun getStringFromInputStream(stream: InputStream): String =
String(stream.readBytes(), StandardCharsets.UTF_8)
+
+ fun DocumentFile.inputStream(): InputStream =
+ YuzuApplication.appContext.contentResolver.openInputStream(uri)!!
+
+ fun DocumentFile.outputStream(): OutputStream =
+ YuzuApplication.appContext.contentResolver.openOutputStream(uri)!!
+
+ fun Uri.inputStream(): InputStream =
+ YuzuApplication.appContext.contentResolver.openInputStream(this)!!
+
+ fun Uri.outputStream(): OutputStream =
+ YuzuApplication.appContext.contentResolver.openOutputStream(this)!!
+
+ fun Uri.asDocumentFile(): DocumentFile? =
+ DocumentFile.fromSingleUri(YuzuApplication.appContext, this)
}
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
import android.net.Uri
import android.os.Build
-import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
@@ -123,7 +122,7 @@ object GpuDriverHelper {
// Unzip the driver.
try {
FileUtil.unzipToInternalStorage(
- BufferedInputStream(copiedFile.inputStream()),
+ copiedFile.path,
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {
@@ -156,7 +155,7 @@ object GpuDriverHelper {
// Unzip the driver to the private installation directory
try {
FileUtil.unzipToInternalStorage(
- BufferedInputStream(driver.inputStream()),
+ driver.path,
File(driverInstallationPath!!)
)
} 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) {
jobject ToJDouble(JNIEnv* env, double value) {
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
}
+
+s32 GetJInteger(JNIEnv* env, jobject jinteger) {
+ return env->GetIntField(jinteger, IDCache::GetIntegerValueField());
+}
+
+jobject ToJInteger(JNIEnv* env, s32 value) {
+ return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value);
+}
+
+bool GetJBoolean(JNIEnv* env, jobject jboolean) {
+ return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField());
+}
+
+jobject ToJBoolean(JNIEnv* env, bool value) {
+ return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value);
+}
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 @@
#include <string>
#include <jni.h>
+#include "common/common_types.h"
std::string GetJString(JNIEnv* env, jstring jstr);
jstring ToJString(JNIEnv* env, std::string_view str);
@@ -13,3 +14,9 @@ jstring ToJString(JNIEnv* env, std::u16string_view str);
double GetJDouble(JNIEnv* env, jobject jdouble);
jobject ToJDouble(JNIEnv* env, double value);
+
+s32 GetJInteger(JNIEnv* env, jobject jinteger);
+jobject ToJInteger(JNIEnv* env, s32 value);
+
+bool GetJBoolean(JNIEnv* env, jobject jboolean);
+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;
static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field;
+static jclass s_patch_class;
+static jmethodID s_patch_constructor;
+static jfieldID s_patch_enabled_field;
+static jfieldID s_patch_name_field;
+static jfieldID s_patch_version_field;
+static jfieldID s_patch_type_field;
+static jfieldID s_patch_program_id_field;
+static jfieldID s_patch_title_id_field;
+
static jclass s_double_class;
static jmethodID s_double_constructor;
static jfieldID s_double_value_field;
+static jclass s_integer_class;
+static jmethodID s_integer_constructor;
+static jfieldID s_integer_value_field;
+
+static jclass s_boolean_class;
+static jmethodID s_boolean_constructor;
+static jfieldID s_boolean_value_field;
+
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace IDCache {
@@ -186,6 +203,38 @@ jfieldID GetOverlayControlDataFoldablePositionField() {
return s_overlay_control_data_foldable_position_field;
}
+jclass GetPatchClass() {
+ return s_patch_class;
+}
+
+jmethodID GetPatchConstructor() {
+ return s_patch_constructor;
+}
+
+jfieldID GetPatchEnabledField() {
+ return s_patch_enabled_field;
+}
+
+jfieldID GetPatchNameField() {
+ return s_patch_name_field;
+}
+
+jfieldID GetPatchVersionField() {
+ return s_patch_version_field;
+}
+
+jfieldID GetPatchTypeField() {
+ return s_patch_type_field;
+}
+
+jfieldID GetPatchProgramIdField() {
+ return s_patch_program_id_field;
+}
+
+jfieldID GetPatchTitleIdField() {
+ return s_patch_title_id_field;
+}
+
jclass GetDoubleClass() {
return s_double_class;
}
@@ -198,6 +247,30 @@ jfieldID GetDoubleValueField() {
return s_double_value_field;
}
+jclass GetIntegerClass() {
+ return s_integer_class;
+}
+
+jmethodID GetIntegerConstructor() {
+ return s_integer_constructor;
+}
+
+jfieldID GetIntegerValueField() {
+ return s_integer_value_field;
+}
+
+jclass GetBooleanClass() {
+ return s_boolean_class;
+}
+
+jmethodID GetBooleanConstructor() {
+ return s_boolean_constructor;
+}
+
+jfieldID GetBooleanValueField() {
+ return s_boolean_value_field;
+}
+
} // namespace IDCache
#ifdef __cplusplus
@@ -278,12 +351,37 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
env->DeleteLocalRef(overlay_control_data_class);
+ const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
+ s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
+ s_patch_constructor = env->GetMethodID(
+ patch_class, "<init>",
+ "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
+ s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
+ s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
+ s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
+ s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
+ s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
+ s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
+ env->DeleteLocalRef(patch_class);
+
const jclass double_class = env->FindClass("java/lang/Double");
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
s_double_value_field = env->GetFieldID(double_class, "value", "D");
env->DeleteLocalRef(double_class);
+ const jclass int_class = env->FindClass("java/lang/Integer");
+ s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
+ s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
+ s_integer_value_field = env->GetFieldID(int_class, "value", "I");
+ env->DeleteLocalRef(int_class);
+
+ const jclass boolean_class = env->FindClass("java/lang/Boolean");
+ s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
+ s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
+ s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
+ env->DeleteLocalRef(boolean_class);
+
// Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
@@ -309,7 +407,10 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_string_class);
env->DeleteGlobalRef(s_pair_class);
env->DeleteGlobalRef(s_overlay_control_data_class);
+ env->DeleteGlobalRef(s_patch_class);
env->DeleteGlobalRef(s_double_class);
+ env->DeleteGlobalRef(s_integer_class);
+ env->DeleteGlobalRef(s_boolean_class);
// UnInitialize applets
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();
jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField();
+jclass GetPatchClass();
+jmethodID GetPatchConstructor();
+jfieldID GetPatchEnabledField();
+jfieldID GetPatchNameField();
+jfieldID GetPatchVersionField();
+jfieldID GetPatchTypeField();
+jfieldID GetPatchProgramIdField();
+jfieldID GetPatchTitleIdField();
+
jclass GetDoubleClass();
jmethodID GetDoubleConstructor();
jfieldID GetDoubleValueField();
+jclass GetIntegerClass();
+jmethodID GetIntegerConstructor();
+jfieldID GetIntegerValueField();
+
+jclass GetBooleanClass();
+jmethodID GetBooleanConstructor();
+jfieldID GetBooleanValueField();
+
} // 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 @@
#include <core/file_sys/patch_manager.h>
#include <core/file_sys/savedata_factory.h>
#include <core/loader/nro.h>
+#include <frontend_common/content_manager.h>
#include <jni.h>
#include "common/detached_tasks.h"
@@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
m_native_window = native_window;
}
-int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
- jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
- std::size_t block_size) {
- if (src == nullptr || dest == nullptr) {
- return false;
- }
- if (!dest->Resize(src->GetSize())) {
- return false;
- }
-
- using namespace Common::Literals;
- [[maybe_unused]] std::vector<u8> buffer(1_MiB);
-
- for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- jconst read = src->Read(buffer.data(), buffer.size(), i);
- dest->Write(buffer.data(), read, i);
- }
- return true;
- };
-
- enum InstallResult {
- Success = 0,
- SuccessFileOverwritten = 1,
- InstallError = 2,
- ErrorBaseGame = 3,
- ErrorFilenameExtension = 4,
- };
-
- [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
- if (file_extension == "nsp") {
- nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
- if (nsp->IsExtractedType()) {
- return InstallError;
- }
- } else {
- return ErrorFilenameExtension;
- }
-
- if (!nsp) {
- return InstallError;
- }
-
- if (nsp->GetStatus() != Loader::ResultStatus::Success) {
- return InstallError;
- }
-
- jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
- copy_func);
-
- switch (res) {
- case FileSys::InstallResult::Success:
- return Success;
- case FileSys::InstallResult::OverwriteExisting:
- return SuccessFileOverwritten;
- case FileSys::InstallResult::ErrorBaseInstall:
- return ErrorBaseGame;
- default:
- return InstallError;
- }
-}
-
void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
const std::string& custom_driver_dir,
const std::string& custom_driver_name,
@@ -512,10 +452,20 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
}
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
- jstring j_file,
- jstring j_file_extension) {
- return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
- GetJString(env, j_file_extension));
+ jstring j_file, jobject jcallback) {
+ auto jlambdaClass = env->GetObjectClass(jcallback);
+ auto jlambdaInvokeMethod = env->GetMethodID(
+ jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) {
+ auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod,
+ ToJDouble(env, max), ToJDouble(env, progress));
+ return GetJBoolean(env, jwasCancelled);
+ };
+
+ return static_cast<int>(
+ ContentManager::InstallNSP(&EmulationSession::GetInstance().System(),
+ EmulationSession::GetInstance().System().GetFilesystem().get(),
+ GetJString(env, j_file), callback));
}
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,
return true;
}
-jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, jobject jobj,
- jstring jpath,
- jstring jprogramId) {
+jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
+ jstring jpath,
+ jstring jprogramId) {
const auto path = GetJString(env, jpath);
const auto vFile =
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
@@ -843,20 +793,40 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
- auto addons = pm.GetPatchVersionNames(update_raw);
- auto jemptyString = ToJString(env, "");
- auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
- jemptyString, jemptyString);
- jobjectArray jaddonsArray =
- env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair);
+ auto patches = pm.GetPatches(update_raw);
+ jobjectArray jpatchArray =
+ env->NewObjectArray(patches.size(), IDCache::GetPatchClass(), nullptr);
int i = 0;
- for (const auto& addon : addons) {
- jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
- ToJString(env, addon.first), ToJString(env, addon.second));
- env->SetObjectArrayElement(jaddonsArray, i, jaddon);
+ for (const auto& patch : patches) {
+ jobject jpatch = env->NewObject(
+ IDCache::GetPatchClass(), IDCache::GetPatchConstructor(), patch.enabled,
+ ToJString(env, patch.name), ToJString(env, patch.version),
+ static_cast<jint>(patch.type), ToJString(env, std::to_string(patch.program_id)),
+ ToJString(env, std::to_string(patch.title_id)));
+ env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i;
}
- return jaddonsArray;
+ return jpatchArray;
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
+ jstring jprogramId) {
+ auto program_id = EmulationSession::GetProgramId(env, jprogramId);
+ ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
+ program_id);
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
+ jstring jprogramId) {
+ auto program_id = EmulationSession::GetProgramId(env, jprogramId);
+ ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id);
+}
+
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
+ jstring jname) {
+ auto program_id = EmulationSession::GetProgramId(env, jprogramId);
+ ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
+ program_id, GetJString(env, jname));
}
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 @@
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/perf_stats.h"
+#include "frontend_common/content_manager.h"
#include "jni/applets/software_keyboard.h"
#include "jni/emu_window/emu_window.h"
#include "video_core/rasterizer_interface.h"
@@ -29,7 +30,6 @@ public:
void SetNativeWindow(ANativeWindow* native_window);
void SurfaceChanged();
- int InstallFileToNand(std::string filename, std::string file_extension);
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir);
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index cb667c928..224ec4d89 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -2,16 +2,16 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
- style="?attr/materialCardViewFilledStyle"
+ style="?attr/materialCardViewElevatedStyle"
android:id="@+id/option_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginHorizontal="12dp"
android:background="?attr/selectableItemBackground"
- android:backgroundTint="?attr/colorSurfaceVariant"
android:clickable="true"
- android:focusable="true">
+ android:focusable="true"
+ app:cardElevation="4dp">
<LinearLayout
android:id="@+id/option_layout"
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 @@
<?xml version="1.0" encoding="utf-8"?>
-<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="24dp"
- app:trackCornerRadius="4dp" />
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/message"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="6dp"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:visibility="gone" />
+
+ <com.google.android.material.progressindicator.LinearProgressIndicator
+ android:id="@+id/progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ app:trackCornerRadius="4dp" />
+
+</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 @@
android:id="@+id/text_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
android:orientation="vertical"
- app:layout_constraintBottom_toBottomOf="@+id/addon_switch"
- app:layout_constraintEnd_toStartOf="@+id/addon_switch"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintEnd_toStartOf="@+id/addon_checkbox"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/addon_switch">
+ app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
@@ -42,16 +41,29 @@
</LinearLayout>
- <com.google.android.material.materialswitch.MaterialSwitch
- android:id="@+id/addon_switch"
+ <com.google.android.material.checkbox.MaterialCheckBox
+ android:id="@+id/addon_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:gravity="center"
- android:nextFocusLeft="@id/addon_container"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginEnd="8dp"
+ app:layout_constraintTop_toTopOf="@+id/text_container"
+ app:layout_constraintBottom_toBottomOf="@+id/text_container"
+ app:layout_constraintEnd_toStartOf="@+id/button_delete" />
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete"
+ app:iconTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@id/text_container"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="@+id/addon_checkbox"
+ app:layout_constraintBottom_toBottomOf="@+id/addon_checkbox" />
</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 @@
<string name="custom">Custom</string>
<string name="notice">Notice</string>
<string name="import_complete">Import complete</string>
+ <string name="more_options">More options</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -348,6 +349,8 @@
<string name="verifying_content">Verifying content…</string>
<string name="content_install_notice">Content install notice</string>
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string>
+ <string name="confirm_uninstall">Confirm uninstall</string>
+ <string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string>
<!-- ROM loading errors -->
<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
return romfs;
}
-PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
+std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
if (title_id == 0) {
return {};
}
- std::map<std::string, std::string, std::less<>> out;
+ std::vector<Patch> out;
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
@@ -482,20 +482,28 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
- const auto update_label = update_disabled ? "[D] Update" : "Update";
+ Patch update_patch = {.enabled = !update_disabled,
+ .name = "Update",
+ .version = "",
+ .type = PatchType::Update,
+ .program_id = title_id,
+ .title_id = title_id};
if (nacp != nullptr) {
- out.insert_or_assign(update_label, nacp->GetVersionString());
+ update_patch.version = nacp->GetVersionString();
+ out.push_back(update_patch);
} else {
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
- out.insert_or_assign(update_label, "");
+ out.push_back(update_patch);
} else {
- out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
+ update_patch.version = FormatTitleVersion(*meta_ver);
+ out.push_back(update_patch);
}
} else if (update_raw != nullptr) {
- out.insert_or_assign(update_label, "PACKED");
+ update_patch.version = "PACKED";
+ out.push_back(update_patch);
}
}
@@ -539,7 +547,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
- out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
+ out.push_back({.enabled = !mod_disabled,
+ .name = mod->GetName(),
+ .version = types,
+ .type = PatchType::Mod,
+ .program_id = title_id,
+ .title_id = title_id});
}
}
@@ -557,7 +570,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
if (!types.empty()) {
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
- out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", types);
+ out.push_back({.enabled = !mod_disabled,
+ .name = "SDMC",
+ .version = types,
+ .type = PatchType::Mod,
+ .program_id = title_id,
+ .title_id = title_id});
}
}
@@ -584,7 +602,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
- out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
+ out.push_back({.enabled = !dlc_disabled,
+ .name = "DLC",
+ .version = std::move(list),
+ .type = PatchType::DLC,
+ .program_id = title_id,
+ .title_id = dlc_match.back().title_id});
}
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;
class NCA;
class NACP;
+enum class PatchType { Update, DLC, Mod };
+
+struct Patch {
+ bool enabled;
+ std::string name;
+ std::string version;
+ PatchType type;
+ u64 program_id;
+ u64 title_id;
+};
+
// A centralized class to manage patches to games.
class PatchManager {
public:
using BuildID = std::array<u8, 0x20>;
using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
- using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
explicit PatchManager(u64 title_id_,
const Service::FileSystem::FileSystemController& fs_controller_,
@@ -66,9 +76,8 @@ public:
VirtualFile packed_update_raw = nullptr,
bool apply_layeredfs = true) const;
- // Returns a vector of pairs between patch names and patch versions.
- // i.e. Update 3.2.2 will return {"Update", "3.2.2"}
- [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
+ // Returns a vector of patches
+ [[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// 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) {
resource_manager->Initialize();
resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(),
true);
+ resource_manager->SetAruidValidForVibration(system.ApplicationProcess()->GetProcessId(), true);
server_manager->RegisterNamedService(
"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 @@
#include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/six_axis/console_six_axis.h"
#include "hid_core/resources/six_axis/seven_six_axis.h"
#include "hid_core/resources/six_axis/six_axis.h"
#include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+#include "hid_core/resources/vibration/vibration_device.h"
namespace Service::HID {
@@ -38,7 +42,7 @@ public:
: ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"},
+ {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"},
};
// clang-format on
@@ -46,22 +50,49 @@ public:
}
private:
- void InitializeVibrationDevice(HLERequestContext& ctx) {
+ void ActivateVibrationDevice(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
- if (resource_manager != nullptr && resource_manager->GetNpad()) {
- resource_manager->GetNpad()->InitializeVibrationDevice(vibration_device_handle);
- }
-
LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
vibration_device_handle.npad_type, vibration_device_handle.npad_id,
vibration_device_handle.device_index);
+ const auto result = ActivateVibrationDeviceImpl(vibration_device_handle);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
+ Result ActivateVibrationDeviceImpl(const Core::HID::VibrationDeviceHandle& handle) {
+ std::scoped_lock lock{mutex};
+
+ const Result is_valid = IsVibrationHandleValid(handle);
+ if (is_valid.IsError()) {
+ return is_valid;
+ }
+
+ for (std::size_t i = 0; i < list_size; i++) {
+ if (handle.device_index == vibration_device_list[i].device_index &&
+ handle.npad_id == vibration_device_list[i].npad_id &&
+ handle.npad_type == vibration_device_list[i].npad_type) {
+ return ResultSuccess;
+ }
+ }
+ if (list_size == vibration_device_list.size()) {
+ return ResultVibrationDeviceIndexOutOfRange;
+ }
+ const Result result = resource_manager->GetVibrationDevice(handle)->Activate();
+ if (result.IsError()) {
+ return result;
+ }
+ vibration_device_list[list_size++] = handle;
+ return ResultSuccess;
+ }
+
+ mutable std::mutex mutex;
+ std::size_t list_size{};
+ std::array<Core::HID::VibrationDeviceHandle, 0x100> vibration_device_list{};
std::shared_ptr<ResourceManager> resource_manager;
};
@@ -153,7 +184,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
{209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"},
{211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
- {212, nullptr, "SendVibrationValueInBool"},
+ {212, &IHidServer::SendVibrationValueInBool, "SendVibrationValueInBool"},
{300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
@@ -1492,59 +1523,13 @@ void IHidServer::ClearNpadCaptureButtonAssignment(HLERequestContext& ctx) {
void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
- const auto controller = GetResourceManager()->GetNpad();
-
- Core::HID::VibrationDeviceInfo vibration_device_info;
- bool check_device_index = false;
-
- switch (vibration_device_handle.npad_type) {
- case Core::HID::NpadStyleIndex::Fullkey:
- case Core::HID::NpadStyleIndex::Handheld:
- case Core::HID::NpadStyleIndex::JoyconDual:
- case Core::HID::NpadStyleIndex::JoyconLeft:
- case Core::HID::NpadStyleIndex::JoyconRight:
- vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
- check_device_index = true;
- break;
- case Core::HID::NpadStyleIndex::GameCube:
- vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
- break;
- case Core::HID::NpadStyleIndex::N64:
- vibration_device_info.type = Core::HID::VibrationDeviceType::N64;
- break;
- default:
- vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
- break;
- }
-
- vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
- if (check_device_index) {
- switch (vibration_device_handle.device_index) {
- case Core::HID::DeviceIndex::Left:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
- break;
- case Core::HID::DeviceIndex::Right:
- vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
- break;
- case Core::HID::DeviceIndex::None:
- default:
- ASSERT_MSG(false, "DeviceIndex should never be None!");
- break;
- }
- }
- LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
- vibration_device_info.type, vibration_device_info.position);
-
- const auto result = IsVibrationHandleValid(vibration_device_handle);
- if (result.IsError()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
+ Core::HID::VibrationDeviceInfo vibration_device_info{};
+ const auto result = GetResourceManager()->GetVibrationDeviceInfo(vibration_device_info,
+ vibration_device_handle);
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
+ rb.Push(result);
rb.PushRaw(vibration_device_info);
}
@@ -1560,16 +1545,16 @@ void IHidServer::SendVibrationValue(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- GetResourceManager()->GetNpad()->VibrateController(parameters.applet_resource_user_id,
- parameters.vibration_device_handle,
- parameters.vibration_value);
-
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+ GetResourceManager()->SendVibrationValue(parameters.applet_resource_user_id,
+ parameters.vibration_device_handle,
+ parameters.vibration_value);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
@@ -1591,10 +1576,28 @@ void IHidServer::GetActualVibrationValue(HLERequestContext& ctx) {
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+ bool has_active_aruid{};
+ NpadVibrationDevice* device{nullptr};
+ Core::HID::VibrationValue vibration_value{};
+ Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
+ has_active_aruid);
+
+ if (result.IsSuccess() && has_active_aruid) {
+ result = IsVibrationHandleValid(parameters.vibration_device_handle);
+ }
+ if (result.IsSuccess() && has_active_aruid) {
+ device = GetResourceManager()->GetNSVibrationDevice(parameters.vibration_device_handle);
+ }
+ if (device != nullptr) {
+ result = device->GetActualVibrationValue(vibration_value);
+ }
+ if (result.IsError()) {
+ vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE;
+ }
+
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess);
- rb.PushRaw(GetResourceManager()->GetNpad()->GetLastVibration(
- parameters.applet_resource_user_id, parameters.vibration_device_handle));
+ rb.PushRaw(vibration_value);
}
void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) {
@@ -1609,25 +1612,27 @@ void IHidServer::PermitVibration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_vibrate{rp.Pop<bool>()};
- // nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value
- // by converting it to a bool
- Settings::values.vibration_enabled.SetValue(can_vibrate);
-
LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume(
+ can_vibrate ? 1.0f : 0.0f);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called");
- // nnSDK checks if a float is greater than zero. We return the bool we stored earlier
- const auto is_enabled = Settings::values.vibration_enabled.GetValue();
+ f32 master_volume{};
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume(
+ master_volume);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(is_enabled);
+ rb.Push(result);
+ rb.Push(master_volume > 0.0f);
}
void IHidServer::SendVibrationValues(HLERequestContext& ctx) {
@@ -1645,13 +1650,22 @@ void IHidServer::SendVibrationValues(HLERequestContext& ctx) {
auto vibration_values = std::span(
reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count);
- GetResourceManager()->GetNpad()->VibrateControllers(applet_resource_user_id,
- vibration_device_handles, vibration_values);
-
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ Result result = ResultSuccess;
+ if (handle_count != vibration_count) {
+ result = ResultVibrationArraySizeMismatch;
+ }
+
+ for (std::size_t i = 0; i < handle_count; i++) {
+ if (result.IsSuccess()) {
+ result = GetResourceManager()->SendVibrationValue(
+ applet_resource_user_id, vibration_device_handles[i], vibration_values[i]);
+ }
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
@@ -1666,43 +1680,6 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- /**
- * Note: This uses yuzu-specific behavior such that the StopHard command produces
- * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below,
- * in order to differentiate between Stop and StopHard commands.
- * This is done to reuse the controller vibration functions made for regular controllers.
- */
- const auto vibration_value = [parameters] {
- switch (parameters.gc_erm_command) {
- case Core::HID::VibrationGcErmCommand::Stop:
- return Core::HID::VibrationValue{
- .low_amplitude = 0.0f,
- .low_frequency = 160.0f,
- .high_amplitude = 0.0f,
- .high_frequency = 320.0f,
- };
- case Core::HID::VibrationGcErmCommand::Start:
- return Core::HID::VibrationValue{
- .low_amplitude = 1.0f,
- .low_frequency = 160.0f,
- .high_amplitude = 1.0f,
- .high_frequency = 320.0f,
- };
- case Core::HID::VibrationGcErmCommand::StopHard:
- return Core::HID::VibrationValue{
- .low_amplitude = 0.0f,
- .low_frequency = 0.0f,
- .high_amplitude = 0.0f,
- .high_frequency = 0.0f,
- };
- default:
- return Core::HID::DEFAULT_VIBRATION_VALUE;
- }
- }();
-
- GetResourceManager()->GetNpad()->VibrateController(
- parameters.applet_resource_user_id, parameters.vibration_device_handle, vibration_value);
-
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
"gc_erm_command={}",
@@ -1711,8 +1688,23 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
parameters.gc_erm_command);
+ bool has_active_aruid{};
+ NpadGcVibrationDevice* gc_device{nullptr};
+ Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
+ has_active_aruid);
+
+ if (result.IsSuccess() && has_active_aruid) {
+ result = IsVibrationHandleValid(parameters.vibration_device_handle);
+ }
+ if (result.IsSuccess() && has_active_aruid) {
+ gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle);
+ }
+ if (gc_device != nullptr) {
+ result = gc_device->SendVibrationGcErmCommand(parameters.gc_erm_command);
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
@@ -1725,33 +1717,31 @@ void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()};
- const auto last_vibration = GetResourceManager()->GetNpad()->GetLastVibration(
- parameters.applet_resource_user_id, parameters.vibration_device_handle);
-
- const auto gc_erm_command = [last_vibration] {
- if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) {
- return Core::HID::VibrationGcErmCommand::Start;
- }
-
- /**
- * Note: This uses yuzu-specific behavior such that the StopHard command produces
- * vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function
- * SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
- * This is done to reuse the controller vibration functions made for regular controllers.
- */
- if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) {
- return Core::HID::VibrationGcErmCommand::StopHard;
- }
-
- return Core::HID::VibrationGcErmCommand::Stop;
- }();
-
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+ bool has_active_aruid{};
+ NpadGcVibrationDevice* gc_device{nullptr};
+ Core::HID::VibrationGcErmCommand gc_erm_command{};
+ Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
+ has_active_aruid);
+
+ if (result.IsSuccess() && has_active_aruid) {
+ result = IsVibrationHandleValid(parameters.vibration_device_handle);
+ }
+ if (result.IsSuccess() && has_active_aruid) {
+ gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle);
+ }
+ if (gc_device != nullptr) {
+ result = gc_device->GetActualVibrationGcErmCommand(gc_erm_command);
+ }
+ if (result.IsError()) {
+ gc_erm_command = Core::HID::VibrationGcErmCommand::Stop;
+ }
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.PushEnum(gc_erm_command);
@@ -1761,21 +1751,24 @@ void IHidServer::BeginPermitVibrationSession(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- GetResourceManager()->GetNpad()->SetPermitVibrationSession(true);
-
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession(
+ applet_resource_user_id);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) {
- GetResourceManager()->GetNpad()->SetPermitVibrationSession(false);
-
LOG_DEBUG(Service_HID, "called");
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession();
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(result);
}
void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) {
@@ -1795,10 +1788,61 @@ void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) {
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+ bool is_mounted{};
+ NpadVibrationBase* device{nullptr};
+ Result result = IsVibrationHandleValid(parameters.vibration_device_handle);
+
+ if (result.IsSuccess()) {
+ device = GetResourceManager()->GetVibrationDevice(parameters.vibration_device_handle);
+ }
+
+ if (device != nullptr) {
+ is_mounted = device->IsVibrationMounted();
+ }
+
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(GetResourceManager()->GetNpad()->IsVibrationDeviceMounted(
- parameters.applet_resource_user_id, parameters.vibration_device_handle));
+ rb.Push(result);
+ rb.Push(is_mounted);
+}
+
+void IHidServer::SendVibrationValueInBool(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::VibrationDeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ bool is_vibrating;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
+ "is_vibrating={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
+ parameters.is_vibrating);
+
+ bool has_active_aruid{};
+ NpadN64VibrationDevice* n64_device{nullptr};
+ Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
+ has_active_aruid);
+
+ if (result.IsSuccess() && has_active_aruid) {
+ result = IsVibrationHandleValid(parameters.vibration_device_handle);
+ }
+ if (result.IsSuccess() && has_active_aruid) {
+ n64_device =
+ GetResourceManager()->GetN64VibrationDevice(parameters.vibration_device_handle);
+ }
+ if (n64_device != nullptr) {
+ result = n64_device->SendValueInBool(parameters.is_vibrating);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
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:
void BeginPermitVibrationSession(HLERequestContext& ctx);
void EndPermitVibrationSession(HLERequestContext& ctx);
void IsVibrationDeviceMounted(HLERequestContext& ctx);
+ void SendVibrationValueInBool(HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(HLERequestContext& ctx);
void StartConsoleSixAxisSensor(HLERequestContext& ctx);
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 @@
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
@@ -67,14 +68,14 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
{501, &IHidSystemServer::RegisterAppletResourceUserId, "RegisterAppletResourceUserId"},
{502, &IHidSystemServer::UnregisterAppletResourceUserId, "UnregisterAppletResourceUserId"},
{503, &IHidSystemServer::EnableAppletToGetInput, "EnableAppletToGetInput"},
- {504, nullptr, "SetAruidValidForVibration"},
+ {504, &IHidSystemServer::SetAruidValidForVibration, "SetAruidValidForVibration"},
{505, &IHidSystemServer::EnableAppletToGetSixAxisSensor, "EnableAppletToGetSixAxisSensor"},
{506, &IHidSystemServer::EnableAppletToGetPadInput, "EnableAppletToGetPadInput"},
{507, &IHidSystemServer::EnableAppletToGetTouchScreen, "EnableAppletToGetTouchScreen"},
- {510, nullptr, "SetVibrationMasterVolume"},
- {511, nullptr, "GetVibrationMasterVolume"},
- {512, nullptr, "BeginPermitVibrationSession"},
- {513, nullptr, "EndPermitVibrationSession"},
+ {510, &IHidSystemServer::SetVibrationMasterVolume, "SetVibrationMasterVolume"},
+ {511, &IHidSystemServer::GetVibrationMasterVolume, "GetVibrationMasterVolume"},
+ {512, &IHidSystemServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
+ {513, &IHidSystemServer::EndPermitVibrationSession, "EndPermitVibrationSession"},
{514, nullptr, "Unknown514"},
{520, nullptr, "EnableHandheldHids"},
{521, nullptr, "DisableHandheldHids"},
@@ -156,7 +157,7 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
{1152, nullptr, "SetTouchScreenDefaultConfiguration"},
{1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
{1154, nullptr, "IsFirmwareAvailableForNotification"},
- {1155, nullptr, "SetForceHandheldStyleVibration"},
+ {1155, &IHidSystemServer::SetForceHandheldStyleVibration, "SetForceHandheldStyleVibration"},
{1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
{1157, nullptr, "CancelConnectionTrigger"},
{1200, nullptr, "IsButtonConfigSupported"},
@@ -538,6 +539,27 @@ void IHidSystemServer::EnableAppletToGetInput(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void IHidSystemServer::SetAruidValidForVibration(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID, "called, is_enabled={}, applet_resource_user_id={}",
+ parameters.is_enabled, parameters.applet_resource_user_id);
+
+ GetResourceManager()->SetAruidValidForVibration(parameters.applet_resource_user_id,
+ parameters.is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void IHidSystemServer::EnableAppletToGetSixAxisSensor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
@@ -601,6 +623,57 @@ void IHidSystemServer::EnableAppletToGetTouchScreen(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void IHidSystemServer::SetVibrationMasterVolume(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto master_volume{rp.Pop<f32>()};
+
+ LOG_INFO(Service_HID, "called, volume={}", master_volume);
+
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume(
+ master_volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidSystemServer::GetVibrationMasterVolume(HLERequestContext& ctx) {
+ f32 master_volume{};
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume(
+ master_volume);
+
+ LOG_INFO(Service_HID, "called, volume={}", master_volume);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(master_volume);
+}
+
+void IHidSystemServer::BeginPermitVibrationSession(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession(
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IHidSystemServer::EndPermitVibrationSession(HLERequestContext& ctx) {
+ LOG_INFO(Service_HID, "called");
+
+ const auto result =
+ GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
void IHidSystemServer::IsJoyConAttachedOnAllRail(HLERequestContext& ctx) {
const bool is_attached = true;
@@ -749,6 +822,19 @@ void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx
rb.PushRaw(touchscreen_config);
}
+void IHidSystemServer::SetForceHandheldStyleVibration(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto is_forced{rp.Pop<bool>()};
+
+ LOG_INFO(Service_HID, "called, is_forced={}", is_forced);
+
+ GetResourceManager()->SetForceHandheldStyleVibration(is_forced);
+ GetResourceManager()->GetNpad()->UpdateHandheldAbstractState();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void IHidSystemServer::IsUsingCustomButtonConfig(HLERequestContext& ctx) {
const bool is_enabled = false;
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:
void RegisterAppletResourceUserId(HLERequestContext& ctx);
void UnregisterAppletResourceUserId(HLERequestContext& ctx);
void EnableAppletToGetInput(HLERequestContext& ctx);
+ void SetAruidValidForVibration(HLERequestContext& ctx);
void EnableAppletToGetSixAxisSensor(HLERequestContext& ctx);
void EnableAppletToGetPadInput(HLERequestContext& ctx);
void EnableAppletToGetTouchScreen(HLERequestContext& ctx);
+ void SetVibrationMasterVolume(HLERequestContext& ctx);
+ void GetVibrationMasterVolume(HLERequestContext& ctx);
+ void BeginPermitVibrationSession(HLERequestContext& ctx);
+ void EndPermitVibrationSession(HLERequestContext& ctx);
void IsJoyConAttachedOnAllRail(HLERequestContext& ctx);
void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx);
void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx);
@@ -61,6 +66,7 @@ private:
void FinalizeUsbFirmwareUpdate(HLERequestContext& ctx);
void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx);
void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx);
+ void SetForceHandheldStyleVibration(HLERequestContext& ctx);
void IsUsingCustomButtonConfig(HLERequestContext& ctx);
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() {
{
const auto lock_guard = Lock();
for (auto& display : displays) {
- for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
- display.GetLayer(layer).GetConsumer().Abandon();
- }
+ display.Abandon();
}
is_abandoned = true;
@@ -176,24 +174,28 @@ void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
}
-void Nvnflinger::OpenLayer(u64 layer_id) {
+bool Nvnflinger::OpenLayer(u64 layer_id) {
const auto lock_guard = Lock();
for (auto& display : displays) {
if (auto* layer = display.FindLayer(layer_id); layer) {
- layer->Open();
+ return layer->Open();
}
}
+
+ return false;
}
-void Nvnflinger::CloseLayer(u64 layer_id) {
+bool Nvnflinger::CloseLayer(u64 layer_id) {
const auto lock_guard = Lock();
for (auto& display : displays) {
if (auto* layer = display.FindLayer(layer_id); layer) {
- layer->Close();
+ return layer->Close();
}
}
+
+ return false;
}
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:
[[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
/// Opens a layer on all displays for the given layer ID.
- void OpenLayer(u64 layer_id);
+ bool OpenLayer(u64 layer_id);
/// Closes a layer on all displays for the given layer ID.
- void CloseLayer(u64 layer_id);
+ bool CloseLayer(u64 layer_id);
/// Destroys the given layer ID.
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,
layers.emplace_back(std::make_unique<Layer>(layer_id, binder_id, *core, *producer,
std::move(buffer_item_consumer)));
+ if (is_abandoned) {
+ this->FindLayer(layer_id)->GetConsumer().Abandon();
+ }
+
hos_binder_driver_server.RegisterProducer(std::move(producer));
}
@@ -103,6 +107,13 @@ void Display::DestroyLayer(u64 layer_id) {
[layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; });
}
+void Display::Abandon() {
+ for (auto& layer : layers) {
+ layer->GetConsumer().Abandon();
+ }
+ is_abandoned = true;
+}
+
Layer* Display::FindLayer(u64 layer_id) {
const auto itr =
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:
layers.clear();
}
+ void Abandon();
+
/// Attempts to find a layer with the given ID.
///
/// @param layer_id The layer ID.
@@ -124,6 +126,7 @@ private:
std::vector<std::unique_ptr<Layer>> layers;
Kernel::KEvent* vsync_event{};
+ bool is_abandoned{};
};
} // 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 @@
#pragma once
#include <memory>
+#include <utility>
#include "common/common_types.h"
@@ -75,12 +76,12 @@ public:
return open;
}
- void Close() {
- open = false;
+ bool Close() {
+ return std::exchange(open, false);
}
- void Open() {
- open = true;
+ bool Open() {
+ return !std::exchange(open, true);
}
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:
return;
}
- nvnflinger.OpenLayer(layer_id);
+ if (!nvnflinger.OpenLayer(layer_id)) {
+ LOG_WARNING(Service_VI, "Tried to open layer which was already open");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultOperationFailed);
+ return;
+ }
android::OutputParcel parcel;
parcel.WriteInterface(NativeWindow{*buffer_queue_id});
@@ -737,7 +742,12 @@ private:
LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id);
- nvnflinger.CloseLayer(layer_id);
+ if (!nvnflinger.CloseLayer(layer_id)) {
+ LOG_WARNING(Service_VI, "Tried to close layer which was not open");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultOperationFailed);
+ return;
+ }
IPC::ResponseBuilder rb{ctx, 2};
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 @@
add_library(frontend_common STATIC
config.cpp
config.h
+ content_manager.h
)
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 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <boost/algorithm/string.hpp>
+#include "common/common_types.h"
+#include "common/literals.h"
+#include "core/core.h"
+#include "core/file_sys/common_funcs.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/submission_package.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/loader.h"
+
+namespace ContentManager {
+
+enum class InstallResult {
+ Success,
+ Overwrite,
+ Failure,
+ BaseInstallAttempted,
+};
+
+/**
+ * \brief Removes a single installed DLC
+ * \param fs_controller [FileSystemController] reference from the Core::System instance
+ * \param title_id Unique title ID representing the DLC which will be removed
+ * \return 'true' if successful
+ */
+inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_controller,
+ const u64 title_id) {
+ return fs_controller.GetUserNANDContents()->RemoveExistingEntry(title_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(title_id);
+}
+
+/**
+ * \brief Removes all DLC for a game
+ * \param system Raw pointer to the system instance
+ * \param program_id Program ID for the game that will have all of its DLC removed
+ * \return Number of DLC removed
+ */
+inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) {
+ size_t count{};
+ const auto& fs_controller = system->GetFileSystemController();
+ const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
+ FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+ std::vector<u64> program_dlc_entries;
+
+ for (const auto& entry : dlc_entries) {
+ if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
+ program_dlc_entries.push_back(entry.title_id);
+ }
+ }
+
+ for (const auto& entry : program_dlc_entries) {
+ if (RemoveDLC(fs_controller, entry)) {
+ ++count;
+ }
+ }
+ return count;
+}
+
+/**
+ * \brief Removes the installed update for a game
+ * \param fs_controller [FileSystemController] reference from the Core::System instance
+ * \param program_id Program ID for the game that will have its installed update removed
+ * \return 'true' if successful
+ */
+inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
+ const u64 program_id) {
+ const auto update_id = program_id | 0x800;
+ return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
+}
+
+/**
+ * \brief Removes the base content for a game
+ * \param fs_controller [FileSystemController] reference from the Core::System instance
+ * \param program_id Program ID for the game that will have its base content removed
+ * \return 'true' if successful
+ */
+inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& fs_controller,
+ const u64 program_id) {
+ return fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
+ fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
+}
+
+/**
+ * \brief Removes a mod for a game
+ * \param fs_controller [FileSystemController] reference from the Core::System instance
+ * \param program_id Program ID for the game where [mod_name] will be removed
+ * \param mod_name The name of a mod as given by FileSys::PatchManager::GetPatches. This corresponds
+ * with the name of the mod's directory in a game's load folder.
+ * \return 'true' if successful
+ */
+inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
+ const u64 program_id, const std::string& mod_name) {
+ // Check general Mods (LayeredFS and IPS)
+ const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id);
+ if (mod_dir != nullptr) {
+ return mod_dir->DeleteSubdirectoryRecursive(mod_name);
+ }
+
+ // Check SDMC mod directory (RomFS LayeredFS)
+ const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id);
+ if (sdmc_mod_dir != nullptr) {
+ return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name);
+ }
+
+ return false;
+}
+
+/**
+ * \brief Installs an NSP
+ * \param system Raw pointer to the system instance
+ * \param vfs Raw pointer to the VfsFilesystem instance in Core::System
+ * \param filename Path to the NSP file
+ * \param callback Optional callback to report the progress of the installation. The first size_t
+ * parameter is the total size of the virtual file and the second is the current progress. If you
+ * return false to the callback, it will cancel the installation as soon as possible.
+ * \return [InstallResult] representing how the installation finished
+ */
+inline InstallResult InstallNSP(
+ Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename,
+ const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) {
+ const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
+ std::size_t block_size) {
+ if (src == nullptr || dest == nullptr) {
+ return false;
+ }
+ if (!dest->Resize(src->GetSize())) {
+ return false;
+ }
+
+ using namespace Common::Literals;
+ std::vector<u8> buffer(1_MiB);
+
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ if (callback(src->GetSize(), i)) {
+ dest->Resize(0);
+ return false;
+ }
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
+ }
+ return true;
+ };
+
+ std::shared_ptr<FileSys::NSP> nsp;
+ FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read);
+ if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) {
+ nsp = std::make_shared<FileSys::NSP>(file);
+ if (nsp->IsExtractedType()) {
+ return InstallResult::Failure;
+ }
+ } else {
+ return InstallResult::Failure;
+ }
+
+ if (nsp->GetStatus() != Loader::ResultStatus::Success) {
+ return InstallResult::Failure;
+ }
+ const auto res =
+ system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy);
+ switch (res) {
+ case FileSys::InstallResult::Success:
+ return InstallResult::Success;
+ case FileSys::InstallResult::OverwriteExisting:
+ return InstallResult::Overwrite;
+ case FileSys::InstallResult::ErrorBaseInstall:
+ return InstallResult::BaseInstallAttempted;
+ default:
+ return InstallResult::Failure;
+ }
+}
+
+/**
+ * \brief Installs an NCA
+ * \param vfs Raw pointer to the VfsFilesystem instance in Core::System
+ * \param filename Path to the NCA file
+ * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to
+ * \param title_type Type of NCA package to install
+ * \param callback Optional callback to report the progress of the installation. The first size_t
+ * parameter is the total size of the virtual file and the second is the current progress. If you
+ * return false to the callback, it will cancel the installation as soon as possible.
+ * \return [InstallResult] representing how the installation finished
+ */
+inline InstallResult InstallNCA(
+ FileSys::VfsFilesystem* vfs, const std::string& filename,
+ FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type,
+ const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) {
+ const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
+ std::size_t block_size) {
+ if (src == nullptr || dest == nullptr) {
+ return false;
+ }
+ if (!dest->Resize(src->GetSize())) {
+ return false;
+ }
+
+ using namespace Common::Literals;
+ std::vector<u8> buffer(1_MiB);
+
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ if (callback(src->GetSize(), i)) {
+ dest->Resize(0);
+ return false;
+ }
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
+ }
+ return true;
+ };
+
+ const auto nca = std::make_shared<FileSys::NCA>(vfs->OpenFile(filename, FileSys::Mode::Read));
+ const auto id = nca->GetStatus();
+
+ // Game updates necessary are missing base RomFS
+ if (id != Loader::ResultStatus::Success &&
+ id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ return InstallResult::Failure;
+ }
+
+ const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy);
+ if (res == FileSys::InstallResult::Success) {
+ return InstallResult::Success;
+ } else if (res == FileSys::InstallResult::OverwriteExisting) {
+ return InstallResult::Overwrite;
+ } else {
+ return InstallResult::Failure;
+ }
+}
+
+} // 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() {
void EmulatedController::LoadDevices() {
// TODO(german77): Use more buttons to detect the correct device
- const auto left_joycon = button_params[Settings::NativeButton::DRight];
- const auto right_joycon = button_params[Settings::NativeButton::A];
+ const auto& left_joycon = button_params[Settings::NativeButton::DRight];
+ const auto& right_joycon = button_params[Settings::NativeButton::A];
// Triggers for GC controllers
trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
@@ -1209,20 +1209,43 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
controller.nfc_state = controller.nfc_values;
}
-bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
+bool EmulatedController::SetVibration(bool should_vibrate) {
+ VibrationValue vibration_value = DEFAULT_VIBRATION_VALUE;
+ if (should_vibrate) {
+ vibration_value.high_amplitude = 1.0f;
+ vibration_value.low_amplitude = 1.0f;
+ }
+
+ return SetVibration(DeviceIndex::Left, vibration_value);
+}
+
+bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand erm_command) {
+ VibrationValue vibration_value = DEFAULT_VIBRATION_VALUE;
+ if (erm_command == Core::HID::VibrationGcErmCommand::Start) {
+ vibration_value.high_amplitude = 1.0f;
+ vibration_value.low_amplitude = 1.0f;
+ }
+
+ return SetVibration(DeviceIndex::Left, vibration_value);
+}
+
+bool EmulatedController::SetVibration(DeviceIndex device_index, const VibrationValue& vibration) {
if (!is_initialized) {
return false;
}
- if (device_index >= output_devices.size()) {
+ if (device_index >= DeviceIndex::MaxDeviceIndex) {
return false;
}
- if (!output_devices[device_index]) {
+ const std::size_t index = static_cast<std::size_t>(device_index);
+ if (!output_devices[index]) {
return false;
}
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f;
+ last_vibration_value = vibration;
+
if (!player.vibration_enabled) {
return false;
}
@@ -1240,8 +1263,11 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
.high_frequency = vibration.high_frequency,
.type = type,
};
- return output_devices[device_index]->SetVibration(status) ==
- Common::Input::DriverResult::Success;
+ return output_devices[index]->SetVibration(status) == Common::Input::DriverResult::Success;
+}
+
+VibrationValue EmulatedController::GetActualVibrationValue(DeviceIndex device_index) const {
+ return last_vibration_value;
}
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:
const NfcState& GetNfc() const;
/**
+ * Sends an on/off vibration to the left device
+ * @return true if vibration had no errors
+ */
+ bool SetVibration(bool should_vibrate);
+
+ /**
+ * Sends an GC vibration to the left device
+ * @return true if vibration had no errors
+ */
+ bool SetVibration(u32 slot, Core::HID::VibrationGcErmCommand erm_command);
+
+ /**
* Sends a specific vibration to the output device
* @return true if vibration had no errors
*/
- bool SetVibration(std::size_t device_index, VibrationValue vibration);
+ bool SetVibration(DeviceIndex device_index, const VibrationValue& vibration);
+
+ /**
+ * @return The last sent vibration
+ */
+ VibrationValue GetActualVibrationValue(DeviceIndex device_index) const;
/**
* Sends a small vibration to the output device
@@ -564,6 +581,7 @@ private:
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0};
std::size_t nfc_handles{0};
+ VibrationValue last_vibration_value{DEFAULT_VIBRATION_VALUE};
// Temporary values to avoid doing changes while the controller is in configuring mode
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 @@
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/service/ipc_helpers.h"
#include "hid_core/hid_core.h"
+#include "hid_core/hid_util.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/applet_resource.h"
@@ -27,6 +28,10 @@
#include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
#include "hid_core/resources/unique_pad/unique_pad.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+#include "hid_core/resources/vibration/vibration_device.h"
namespace Service::HID {
@@ -52,6 +57,7 @@ void ResourceManager::Initialize() {
system.HIDCore().ReloadInputDevices();
+ handheld_config = std::make_shared<HandheldConfig>();
InitializeHidCommonSampler();
InitializeTouchScreenSampler();
InitializeConsoleSixAxisSampler();
@@ -174,7 +180,7 @@ void ResourceManager::InitializeHidCommonSampler() {
debug_pad->SetAppletResource(applet_resource, &shared_mutex);
digitizer->SetAppletResource(applet_resource, &shared_mutex);
keyboard->SetAppletResource(applet_resource, &shared_mutex);
- npad->SetNpadExternals(applet_resource, &shared_mutex);
+ npad->SetNpadExternals(applet_resource, &shared_mutex, handheld_config);
six_axis->SetAppletResource(applet_resource, &shared_mutex);
mouse->SetAppletResource(applet_resource, &shared_mutex);
debug_mouse->SetAppletResource(applet_resource, &shared_mutex);
@@ -257,6 +263,121 @@ void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) {
applet_resource->EnableTouchScreen(aruid, is_enabled);
}
+NpadVibrationBase* ResourceManager::GetVibrationDevice(
+ const Core::HID::VibrationDeviceHandle& handle) {
+ return npad->GetVibrationDevice(handle);
+}
+
+NpadN64VibrationDevice* ResourceManager::GetN64VibrationDevice(
+ const Core::HID::VibrationDeviceHandle& handle) {
+ return npad->GetN64VibrationDevice(handle);
+}
+
+NpadVibrationDevice* ResourceManager::GetNSVibrationDevice(
+ const Core::HID::VibrationDeviceHandle& handle) {
+ return npad->GetNSVibrationDevice(handle);
+}
+
+NpadGcVibrationDevice* ResourceManager::GetGcVibrationDevice(
+ const Core::HID::VibrationDeviceHandle& handle) {
+ return npad->GetGcVibrationDevice(handle);
+}
+
+Result ResourceManager::SetAruidValidForVibration(u64 aruid, bool is_enabled) {
+ std::scoped_lock lock{shared_mutex};
+ const bool has_changed = applet_resource->SetAruidValidForVibration(aruid, is_enabled);
+
+ if (has_changed) {
+ auto devices = npad->GetAllVibrationDevices();
+ for ([[maybe_unused]] auto* device : devices) {
+ // TODO
+ }
+ }
+
+ auto* vibration_handler = npad->GetVibrationHandler();
+ if (aruid != vibration_handler->GetSessionAruid()) {
+ vibration_handler->EndPermitVibrationSession();
+ }
+
+ return ResultSuccess;
+}
+
+void ResourceManager::SetForceHandheldStyleVibration(bool is_forced) {
+ handheld_config->is_force_handheld_style_vibration = is_forced;
+}
+
+Result ResourceManager::IsVibrationAruidActive(u64 aruid, bool& is_active) const {
+ std::scoped_lock lock{shared_mutex};
+ is_active = applet_resource->IsVibrationAruidActive(aruid);
+ return ResultSuccess;
+}
+
+Result ResourceManager::GetVibrationDeviceInfo(Core::HID::VibrationDeviceInfo& device_info,
+ const Core::HID::VibrationDeviceHandle& handle) {
+ bool check_device_index = false;
+
+ const Result is_valid = IsVibrationHandleValid(handle);
+ if (is_valid.IsError()) {
+ return is_valid;
+ }
+
+ switch (handle.npad_type) {
+ case Core::HID::NpadStyleIndex::Fullkey:
+ case Core::HID::NpadStyleIndex::Handheld:
+ case Core::HID::NpadStyleIndex::JoyconDual:
+ case Core::HID::NpadStyleIndex::JoyconLeft:
+ case Core::HID::NpadStyleIndex::JoyconRight:
+ device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
+ check_device_index = true;
+ break;
+ case Core::HID::NpadStyleIndex::GameCube:
+ device_info.type = Core::HID::VibrationDeviceType::GcErm;
+ break;
+ case Core::HID::NpadStyleIndex::N64:
+ device_info.type = Core::HID::VibrationDeviceType::N64;
+ break;
+ default:
+ device_info.type = Core::HID::VibrationDeviceType::Unknown;
+ break;
+ }
+
+ device_info.position = Core::HID::VibrationDevicePosition::None;
+ if (check_device_index) {
+ switch (handle.device_index) {
+ case Core::HID::DeviceIndex::Left:
+ device_info.position = Core::HID::VibrationDevicePosition::Left;
+ break;
+ case Core::HID::DeviceIndex::Right:
+ device_info.position = Core::HID::VibrationDevicePosition::Right;
+ break;
+ case Core::HID::DeviceIndex::None:
+ default:
+ ASSERT_MSG(false, "DeviceIndex should never be None!");
+ break;
+ }
+ }
+ return ResultSuccess;
+}
+
+Result ResourceManager::SendVibrationValue(u64 aruid,
+ const Core::HID::VibrationDeviceHandle& handle,
+ const Core::HID::VibrationValue& value) {
+ bool has_active_aruid{};
+ NpadVibrationDevice* device{nullptr};
+ Result result = IsVibrationAruidActive(aruid, has_active_aruid);
+
+ if (result.IsSuccess() && has_active_aruid) {
+ result = IsVibrationHandleValid(handle);
+ }
+ if (result.IsSuccess() && has_active_aruid) {
+ device = GetNSVibrationDevice(handle);
+ }
+ if (device != nullptr) {
+ result = device->SendVibrationValue(value);
+ }
+ return result;
+}
+
void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
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 {
class System;
}
+namespace Core::HID {
+struct VibrationDeviceHandle;
+struct VibrationValue;
+struct VibrationDeviceInfo;
+} // namespace Core::HID
+
namespace Core::Timing {
struct EventType;
}
@@ -37,6 +43,11 @@ class SixAxis;
class SleepButton;
class TouchScreen;
class UniquePad;
+class NpadVibrationBase;
+class NpadN64VibrationDevice;
+class NpadGcVibrationDevice;
+class NpadVibrationDevice;
+struct HandheldConfig;
class ResourceManager {
@@ -79,6 +90,18 @@ public:
void EnablePadInput(u64 aruid, bool is_enabled);
void EnableTouchScreen(u64 aruid, bool is_enabled);
+ NpadVibrationBase* GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadN64VibrationDevice* GetN64VibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadVibrationDevice* GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadGcVibrationDevice* GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ Result SetAruidValidForVibration(u64 aruid, bool is_enabled);
+ void SetForceHandheldStyleVibration(bool is_forced);
+ Result IsVibrationAruidActive(u64 aruid, bool& is_active) const;
+ Result GetVibrationDeviceInfo(Core::HID::VibrationDeviceInfo& device_info,
+ const Core::HID::VibrationDeviceHandle& handle);
+ Result SendVibrationValue(u64 aruid, const Core::HID::VibrationDeviceHandle& handle,
+ const Core::HID::VibrationValue& value);
+
void UpdateControllers(std::chrono::nanoseconds ns_late);
void UpdateNpad(std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late);
@@ -113,6 +136,8 @@ private:
std::shared_ptr<TouchScreen> touch_screen = nullptr;
std::shared_ptr<UniquePad> unique_pad = nullptr;
+ std::shared_ptr<HandheldConfig> handheld_config = nullptr;
+
// TODO: Create these resources
// std::shared_ptr<AudioControl> audio_control = nullptr;
// 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)
if (sensor_state < NpadIrSensorState::Available) {
return ResultIrSensorIsNotReady;
}
- handle = xcd_handle;
+ // handle = xcd_handle;
return ResultSuccess;
}
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 @@
#include "core/hle/result.h"
#include "hid_core/hid_types.h"
+namespace Core::HID {
+class EmulatedController;
+}
+
namespace Kernel {
class KEvent;
class KReadableEvent;
@@ -50,7 +54,7 @@ private:
s32 ref_counter{};
Kernel::KEvent* ir_sensor_event{nullptr};
- u64 xcd_handle{};
+ Core::HID::EmulatedController* xcd_handle{};
NpadIrSensorState sensor_state{};
};
} // 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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "hid_core/hid_core.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/abstracted_pad/abstract_pad.h"
#include "hid_core/resources/applet_resource.h"
@@ -16,7 +17,7 @@ void AbstractPad::SetExternals(AppletResourceHolder* applet_resource,
CaptureButtonResource* capture_button_resource,
HomeButtonResource* home_button_resource,
SixAxisResource* sixaxis_resource, PalmaResource* palma_resource,
- VibrationHandler* vibration) {
+ NpadVibration* vibration, Core::HID::HIDCore* core) {
applet_resource_holder = applet_resource;
properties_handler.SetAppletResource(applet_resource_holder);
@@ -35,13 +36,14 @@ void AbstractPad::SetExternals(AppletResourceHolder* applet_resource,
mcu_handler.SetAbstractPadHolder(&abstract_pad_holder);
mcu_handler.SetPropertiesHandler(&properties_handler);
- std::array<NpadVibrationDevice*, 2> vibration_devices{&vibration_left, &vibration_right};
vibration_handler.SetAppletResource(applet_resource_holder);
vibration_handler.SetAbstractPadHolder(&abstract_pad_holder);
vibration_handler.SetPropertiesHandler(&properties_handler);
vibration_handler.SetN64Vibration(&vibration_n64);
- vibration_handler.SetVibration(vibration_devices);
+ vibration_handler.SetVibration(&vibration_left, &vibration_right);
vibration_handler.SetGcVibration(&vibration_gc);
+ vibration_handler.SetVibrationHandler(vibration);
+ vibration_handler.SetHidCore(core);
sixaxis_handler.SetAppletResource(applet_resource_holder);
sixaxis_handler.SetAbstractPadHolder(&abstract_pad_holder);
@@ -239,11 +241,6 @@ NpadVibrationDevice* AbstractPad::GetVibrationDevice(Core::HID::DeviceIndex devi
return &vibration_left;
}
-void AbstractPad::GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list) {
- list.emplace_back(&vibration_left);
- list.emplace_back(&vibration_right);
-}
-
NpadGcVibrationDevice* AbstractPad::GetGCVibrationDevice() {
return &vibration_gc;
}
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;
class SixAxisResource;
class PalmaResource;
class NPadResource;
-class AbstractPad;
class NpadLastActiveHandler;
class NpadIrNfcHandler;
class UniquePads;
@@ -44,7 +43,6 @@ class NpadGcVibration;
class CaptureButtonResource;
class HomeButtonResource;
-class VibrationHandler;
struct HandheldConfig;
@@ -57,7 +55,8 @@ public:
void SetExternals(AppletResourceHolder* applet_resource,
CaptureButtonResource* capture_button_resource,
HomeButtonResource* home_button_resource, SixAxisResource* sixaxis_resource,
- PalmaResource* palma_resource, VibrationHandler* vibration);
+ PalmaResource* palma_resource, NpadVibration* vibration,
+ Core::HID::HIDCore* core);
void SetNpadId(Core::HID::NpadIdType npad_id);
Result Activate();
@@ -78,7 +77,6 @@ public:
NpadN64VibrationDevice* GetN64VibrationDevice();
NpadVibrationDevice* GetVibrationDevice(Core::HID::DeviceIndex device_index);
- void GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list);
NpadGcVibrationDevice* GetGCVibrationDevice();
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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "hid_core/frontend/emulated_controller.h"
+#include "hid_core/hid_core.h"
#include "hid_core/hid_result.h"
#include "hid_core/hid_util.h"
#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
@@ -30,14 +32,22 @@ void NpadAbstractVibrationHandler::SetPropertiesHandler(NpadAbstractPropertiesHa
properties_handler = handler;
}
+void NpadAbstractVibrationHandler::SetVibrationHandler(NpadVibration* handler) {
+ vibration_handler = handler;
+}
+
+void NpadAbstractVibrationHandler::SetHidCore(Core::HID::HIDCore* core) {
+ hid_core = core;
+}
+
void NpadAbstractVibrationHandler::SetN64Vibration(NpadN64VibrationDevice* n64_device) {
n64_vibration_device = n64_device;
}
-void NpadAbstractVibrationHandler::SetVibration(std::span<NpadVibrationDevice*> device) {
- for (std::size_t i = 0; i < device.size() && i < vibration_device.size(); i++) {
- vibration_device[i] = device[i];
- }
+void NpadAbstractVibrationHandler::SetVibration(NpadVibrationDevice* left_device,
+ NpadVibrationDevice* right_device) {
+ left_vibration_device = left_device;
+ right_vibration_device = right_device;
}
void NpadAbstractVibrationHandler::SetGcVibration(NpadGcVibrationDevice* gc_device) {
@@ -69,5 +79,29 @@ void NpadAbstractVibrationHandler::UpdateVibrationState() {
if (!is_handheld_hid_enabled && is_force_handheld_style_vibration) {
// TODO
}
+
+ // TODO: This function isn't accurate. It's supposed to get 5 abstracted pads from the
+ // NpadAbstractPropertiesHandler but this handler isn't fully implemented yet
+ IAbstractedPad abstracted_pad{};
+ const auto npad_id = properties_handler->GetNpadId();
+ abstracted_pad.xcd_handle = hid_core->GetEmulatedController(npad_id);
+ abstracted_pad.internal_flags.is_connected.Assign(abstracted_pad.xcd_handle->IsConnected());
+
+ if (abstracted_pad.internal_flags.is_connected) {
+ left_vibration_device->Mount(abstracted_pad, Core::HID::DeviceIndex::Left,
+ vibration_handler);
+ right_vibration_device->Mount(abstracted_pad, Core::HID::DeviceIndex::Right,
+ vibration_handler);
+ gc_vibration_device->Mount(abstracted_pad, 0, vibration_handler);
+ gc_vibration_device->Mount(abstracted_pad, 0, vibration_handler);
+ n64_vibration_device->Mount(abstracted_pad, vibration_handler);
+ return;
+ }
+
+ left_vibration_device->Unmount();
+ right_vibration_device->Unmount();
+ gc_vibration_device->Unmount();
+ gc_vibration_device->Unmount();
+ n64_vibration_device->Unmount();
}
} // 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 @@
#include "core/hle/result.h"
#include "hid_core/hid_types.h"
+namespace Core::HID {
+class HIDCore;
+}
+
namespace Service::HID {
struct AppletResourceHolder;
class NpadAbstractedPadHolder;
@@ -27,9 +31,11 @@ public:
void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
void SetAppletResource(AppletResourceHolder* applet_resource);
void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+ void SetVibrationHandler(NpadVibration* handler);
+ void SetHidCore(Core::HID::HIDCore* core);
void SetN64Vibration(NpadN64VibrationDevice* n64_device);
- void SetVibration(std::span<NpadVibrationDevice*> device);
+ void SetVibration(NpadVibrationDevice* left_device, NpadVibrationDevice* right_device);
void SetGcVibration(NpadGcVibrationDevice* gc_device);
Result IncrementRefCounter();
@@ -41,9 +47,11 @@ private:
AppletResourceHolder* applet_resource_holder{nullptr};
NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
NpadAbstractPropertiesHandler* properties_handler{nullptr};
+ Core::HID::HIDCore* hid_core{nullptr};
NpadN64VibrationDevice* n64_vibration_device{nullptr};
- std::array<NpadVibrationDevice*, 2> vibration_device{};
+ NpadVibrationDevice* left_vibration_device{};
+ NpadVibrationDevice* right_vibration_device{};
NpadGcVibrationDevice* gc_vibration_device{nullptr};
NpadVibration* vibration_handler{nullptr};
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) {
data[index].flag.enable_touchscreen.Assign(is_enabled);
}
+bool AppletResource::SetAruidValidForVibration(u64 aruid, bool is_enabled) {
+ const u64 index = GetIndexFromAruid(aruid);
+ if (index >= AruidIndexMax) {
+ return false;
+ }
+
+ if (!is_enabled && aruid == active_vibration_aruid) {
+ active_vibration_aruid = SystemAruid;
+ return true;
+ }
+
+ if (is_enabled && aruid != active_vibration_aruid) {
+ active_vibration_aruid = aruid;
+ return true;
+ }
+
+ return false;
+}
+
void AppletResource::EnableSixAxisSensor(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
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:
Result DestroySevenSixAxisTransferMemory();
void EnableInput(u64 aruid, bool is_enabled);
+ bool SetAruidValidForVibration(u64 aruid, bool is_enabled);
void EnableSixAxisSensor(u64 aruid, bool is_enabled);
void EnablePadInput(u64 aruid, bool is_enabled);
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 @@
#include "hid_core/hid_util.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/npad/npad.h"
+#include "hid_core/resources/npad/npad_vibration.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
@@ -31,10 +32,6 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
auto& controller = controller_data[aruid_index][i];
controller.device = hid_core.GetEmulatedControllerByIndex(i);
- controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value =
- Core::HID::DEFAULT_VIBRATION_VALUE;
- controller.vibration[Core::HID::EmulatedDeviceIndex::RightIndex]
- .latest_vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE;
Core::HID::ControllerUpdateCallback engine_callback{
.on_change =
[this, i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); },
@@ -43,6 +40,10 @@ NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service
controller.callback_key = controller.device->SetCallback(engine_callback);
}
}
+ for (std::size_t i = 0; i < abstracted_pads.size(); ++i) {
+ abstracted_pads[i] = AbstractPad{};
+ abstracted_pads[i].SetNpadId(IndexToNpadIdType(i));
+ }
}
NPad::~NPad() {
@@ -359,6 +360,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id);
WriteEmptyEntry(controller.shared_memory);
hid_core.SetLastActiveController(npad_id);
+ abstracted_pads[NpadIdTypeToIndex(npad_id)].Update();
}
void NPad::WriteEmptyEntry(NpadInternalState* npad) {
@@ -740,171 +742,6 @@ bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID:
return true;
}
-bool NPad::VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
- std::size_t device_index,
- const Core::HID::VibrationValue& vibration_value) {
- auto& controller = GetControllerFromNpadIdType(aruid, npad_id);
- if (!controller.device->IsConnected()) {
- return false;
- }
-
- if (!controller.device->IsVibrationEnabled(device_index)) {
- if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f ||
- controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) {
- // Send an empty vibration to stop any vibrations.
- Core::HID::VibrationValue vibration{0.0f, 160.0f, 0.0f, 320.0f};
- controller.device->SetVibration(device_index, vibration);
- // Then reset the vibration value to its default value.
- controller.vibration[device_index].latest_vibration_value =
- Core::HID::DEFAULT_VIBRATION_VALUE;
- }
-
- return false;
- }
-
- if (!Settings::values.enable_accurate_vibrations.GetValue()) {
- using std::chrono::duration_cast;
- using std::chrono::milliseconds;
- using std::chrono::steady_clock;
-
- const auto now = steady_clock::now();
-
- // Filter out non-zero vibrations that are within 15ms of each other.
- if ((vibration_value.low_amplitude != 0.0f || vibration_value.high_amplitude != 0.0f) &&
- duration_cast<milliseconds>(
- now - controller.vibration[device_index].last_vibration_timepoint) <
- milliseconds(15)) {
- return false;
- }
-
- controller.vibration[device_index].last_vibration_timepoint = now;
- }
-
- Core::HID::VibrationValue vibration{
- vibration_value.low_amplitude, vibration_value.low_frequency,
- vibration_value.high_amplitude, vibration_value.high_frequency};
- return controller.device->SetVibration(device_index, vibration);
-}
-
-void NPad::VibrateController(u64 aruid,
- const Core::HID::VibrationDeviceHandle& vibration_device_handle,
- const Core::HID::VibrationValue& vibration_value) {
- if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
- return;
- }
-
- if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
- return;
- }
-
- auto& controller = GetControllerFromHandle(aruid, vibration_device_handle);
- const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
-
- if (!controller.vibration[device_index].device_mounted || !controller.device->IsConnected()) {
- return;
- }
-
- if (vibration_device_handle.device_index == Core::HID::DeviceIndex::None) {
- ASSERT_MSG(false, "DeviceIndex should never be None!");
- return;
- }
-
- // Some games try to send mismatched parameters in the device handle, block these.
- if ((controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft &&
- (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconRight ||
- vibration_device_handle.device_index == Core::HID::DeviceIndex::Right)) ||
- (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight &&
- (vibration_device_handle.npad_type == Core::HID::NpadStyleIndex::JoyconLeft ||
- vibration_device_handle.device_index == Core::HID::DeviceIndex::Left))) {
- return;
- }
-
- // Filter out vibrations with equivalent values to reduce unnecessary state changes.
- if (vibration_value.low_amplitude ==
- controller.vibration[device_index].latest_vibration_value.low_amplitude &&
- vibration_value.high_amplitude ==
- controller.vibration[device_index].latest_vibration_value.high_amplitude) {
- return;
- }
-
- if (VibrateControllerAtIndex(aruid, controller.device->GetNpadIdType(), device_index,
- vibration_value)) {
- controller.vibration[device_index].latest_vibration_value = vibration_value;
- }
-}
-
-void NPad::VibrateControllers(
- u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
- std::span<const Core::HID::VibrationValue> vibration_values) {
- if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
- return;
- }
-
- ASSERT_OR_EXECUTE_MSG(
- vibration_device_handles.size() == vibration_values.size(), { return; },
- "The amount of device handles does not match with the amount of vibration values,"
- "this is undefined behavior!");
-
- for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) {
- VibrateController(aruid, vibration_device_handles[i], vibration_values[i]);
- }
-}
-
-Core::HID::VibrationValue NPad::GetLastVibration(
- u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
- return {};
- }
-
- const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle);
- const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
- return controller.vibration[device_index].latest_vibration_value;
-}
-
-void NPad::InitializeVibrationDevice(
- const Core::HID::VibrationDeviceHandle& vibration_device_handle) {
- if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
- return;
- }
-
- const auto aruid = applet_resource_holder.applet_resource->GetActiveAruid();
- const auto npad_index = static_cast<Core::HID::NpadIdType>(vibration_device_handle.npad_id);
- const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
-
- if (aruid == 0) {
- return;
- }
-
- InitializeVibrationDeviceAtIndex(aruid, npad_index, device_index);
-}
-
-void NPad::InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
- std::size_t device_index) {
- auto& controller = GetControllerFromNpadIdType(aruid, npad_id);
- if (!Settings::values.vibration_enabled.GetValue()) {
- controller.vibration[device_index].device_mounted = false;
- return;
- }
-
- controller.vibration[device_index].device_mounted =
- controller.device->IsVibrationEnabled(device_index);
-}
-
-void NPad::SetPermitVibrationSession(bool permit_vibration_session) {
- permit_vibration_session_enabled = permit_vibration_session;
-}
-
-bool NPad::IsVibrationDeviceMounted(
- u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const {
- if (IsVibrationHandleValid(vibration_device_handle).IsError()) {
- return false;
- }
-
- const auto& controller = GetControllerFromHandle(aruid, vibration_device_handle);
- const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
- return controller.vibration[device_index].device_mounted;
-}
-
Result NPad::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event,
Core::HID::NpadIdType npad_id) {
std::scoped_lock lock{mutex};
@@ -936,11 +773,6 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) {
LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id);
auto& controller = GetControllerFromNpadIdType(aruid, npad_id);
- for (std::size_t device_idx = 0; device_idx < controller.vibration.size(); ++device_idx) {
- // Send an empty vibration to stop any vibrations.
- VibrateControllerAtIndex(aruid, npad_id, device_idx, {});
- controller.vibration[device_idx].device_mounted = false;
- }
auto* shared_memory = controller.shared_memory;
// Don't reset shared_memory->assignment_mode this value is persistent
@@ -1243,22 +1075,17 @@ void NPad::UnregisterAppletResourceUserId(u64 aruid) {
}
void NPad::SetNpadExternals(std::shared_ptr<AppletResource> resource,
- std::recursive_mutex* shared_mutex) {
+ std::recursive_mutex* shared_mutex,
+ std::shared_ptr<HandheldConfig> handheld_config) {
applet_resource_holder.applet_resource = resource;
applet_resource_holder.shared_mutex = shared_mutex;
applet_resource_holder.shared_npad_resource = &npad_resource;
-}
+ applet_resource_holder.handheld_config = handheld_config;
-NPad::NpadControllerData& NPad::GetControllerFromHandle(
- u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) {
- const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
- return GetControllerFromNpadIdType(aruid, npad_id);
-}
-
-const NPad::NpadControllerData& NPad::GetControllerFromHandle(
- u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const {
- const auto npad_id = static_cast<Core::HID::NpadIdType>(device_handle.npad_id);
- return GetControllerFromNpadIdType(aruid, npad_id);
+ for (auto& abstract_pad : abstracted_pads) {
+ abstract_pad.SetExternals(&applet_resource_holder, nullptr, nullptr, nullptr, nullptr,
+ &vibration_handler, &hid_core);
+ }
}
NPad::NpadControllerData& NPad::GetControllerFromHandle(
@@ -1396,4 +1223,97 @@ Result NPad::GetLastActiveNpad(Core::HID::NpadIdType& out_npad_id) const {
return ResultSuccess;
}
+NpadVibration* NPad::GetVibrationHandler() {
+ return &vibration_handler;
+}
+
+std::vector<NpadVibrationBase*> NPad::GetAllVibrationDevices() {
+ std::vector<NpadVibrationBase*> vibration_devices;
+
+ for (auto& abstract_pad : abstracted_pads) {
+ auto* left_device = abstract_pad.GetVibrationDevice(Core::HID::DeviceIndex::Left);
+ auto* right_device = abstract_pad.GetVibrationDevice(Core::HID::DeviceIndex::Right);
+ auto* n64_device = abstract_pad.GetGCVibrationDevice();
+ auto* gc_device = abstract_pad.GetGCVibrationDevice();
+
+ if (left_device != nullptr) {
+ vibration_devices.emplace_back(left_device);
+ }
+ if (right_device != nullptr) {
+ vibration_devices.emplace_back(right_device);
+ }
+ if (n64_device != nullptr) {
+ vibration_devices.emplace_back(n64_device);
+ }
+ if (gc_device != nullptr) {
+ vibration_devices.emplace_back(gc_device);
+ }
+ }
+
+ return vibration_devices;
+}
+
+NpadVibrationBase* NPad::GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) {
+ if (IsVibrationHandleValid(handle).IsError()) {
+ return nullptr;
+ }
+
+ const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id));
+ const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type);
+ if (style_inde == Core::HID::NpadStyleIndex::GameCube) {
+ return abstracted_pads[npad_index].GetGCVibrationDevice();
+ }
+ if (style_inde == Core::HID::NpadStyleIndex::N64) {
+ return abstracted_pads[npad_index].GetN64VibrationDevice();
+ }
+ return abstracted_pads[npad_index].GetVibrationDevice(handle.device_index);
+}
+
+NpadN64VibrationDevice* NPad::GetN64VibrationDevice(
+ const Core::HID::VibrationDeviceHandle& handle) {
+ if (IsVibrationHandleValid(handle).IsError()) {
+ return nullptr;
+ }
+
+ const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id));
+ const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type);
+ if (style_inde != Core::HID::NpadStyleIndex::N64) {
+ return nullptr;
+ }
+ return abstracted_pads[npad_index].GetN64VibrationDevice();
+}
+
+NpadVibrationDevice* NPad::GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) {
+ if (IsVibrationHandleValid(handle).IsError()) {
+ return nullptr;
+ }
+
+ const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id));
+ const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type);
+ if (style_inde == Core::HID::NpadStyleIndex::GameCube ||
+ style_inde == Core::HID::NpadStyleIndex::N64) {
+ return nullptr;
+ }
+
+ return abstracted_pads[npad_index].GetVibrationDevice(handle.device_index);
+}
+
+NpadGcVibrationDevice* NPad::GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle) {
+ if (IsVibrationHandleValid(handle).IsError()) {
+ return nullptr;
+ }
+
+ const auto npad_index = NpadIdTypeToIndex(static_cast<Core::HID::NpadIdType>(handle.npad_id));
+ const auto style_inde = static_cast<Core::HID::NpadStyleIndex>(handle.npad_type);
+ if (style_inde != Core::HID::NpadStyleIndex::GameCube) {
+ return nullptr;
+ }
+ return abstracted_pads[npad_index].GetGCVibrationDevice();
+}
+
+void NPad::UpdateHandheldAbstractState() {
+ std::scoped_lock lock{mutex};
+ abstracted_pads[NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld)].Update();
+}
+
} // 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 @@
#include "common/common_types.h"
#include "hid_core/hid_types.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad.h"
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/npad/npad_resource.h"
#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+#include "hid_core/resources/vibration/vibration_device.h"
namespace Core::HID {
class EmulatedController;
@@ -32,6 +38,7 @@ union Result;
namespace Service::HID {
class AppletResource;
+struct HandheldConfig;
struct NpadInternalState;
struct NpadSixAxisSensorLifo;
struct NpadSharedMemoryFormat;
@@ -68,31 +75,6 @@ public:
bool SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id,
NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode);
- bool VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
- std::size_t device_index,
- const Core::HID::VibrationValue& vibration_value);
-
- void VibrateController(u64 aruid,
- const Core::HID::VibrationDeviceHandle& vibration_device_handle,
- const Core::HID::VibrationValue& vibration_value);
-
- void VibrateControllers(
- u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
- std::span<const Core::HID::VibrationValue> vibration_values);
-
- Core::HID::VibrationValue GetLastVibration(
- u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
-
- void InitializeVibrationDevice(const Core::HID::VibrationDeviceHandle& vibration_device_handle);
-
- void InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
- std::size_t device_index);
-
- void SetPermitVibrationSession(bool permit_vibration_session);
-
- bool IsVibrationDeviceMounted(
- u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
-
Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event,
Core::HID::NpadIdType npad_id);
@@ -145,7 +127,8 @@ public:
Result RegisterAppletResourceUserId(u64 aruid);
void UnregisterAppletResourceUserId(u64 aruid);
void SetNpadExternals(std::shared_ptr<AppletResource> resource,
- std::recursive_mutex* shared_mutex);
+ std::recursive_mutex* shared_mutex,
+ std::shared_ptr<HandheldConfig> handheld_config);
AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id);
@@ -161,18 +144,20 @@ public:
Result GetLastActiveNpad(Core::HID::NpadIdType& out_npad_id) const;
-private:
- struct VibrationData {
- bool device_mounted{};
- Core::HID::VibrationValue latest_vibration_value{};
- std::chrono::steady_clock::time_point last_vibration_timepoint{};
- };
+ NpadVibration* GetVibrationHandler();
+ std::vector<NpadVibrationBase*> GetAllVibrationDevices();
+ NpadVibrationBase* GetVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadN64VibrationDevice* GetN64VibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadVibrationDevice* GetNSVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ NpadGcVibrationDevice* GetGcVibrationDevice(const Core::HID::VibrationDeviceHandle& handle);
+ void UpdateHandheldAbstractState();
+
+private:
struct NpadControllerData {
NpadInternalState* shared_memory = nullptr;
Core::HID::EmulatedController* device = nullptr;
- std::array<VibrationData, 2> vibration{};
bool is_connected{};
// Dual joycons can have only one side connected
@@ -192,10 +177,6 @@ private:
void WriteEmptyEntry(NpadInternalState* npad);
NpadControllerData& GetControllerFromHandle(
- u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle);
- const NpadControllerData& GetControllerFromHandle(
- u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const;
- NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const;
@@ -215,11 +196,13 @@ private:
mutable std::mutex mutex;
NPadResource npad_resource;
AppletResourceHolder applet_resource_holder{};
+ std::array<AbstractPad, MaxSupportedNpadIdTypes> abstracted_pads;
+ NpadVibration vibration_handler{};
+
Kernel::KEvent* input_event{nullptr};
std::mutex* input_mutex{nullptr};
std::atomic<u64> press_state{};
- bool permit_vibration_session_enabled;
std::array<std::array<NpadControllerData, MaxSupportedNpadIdTypes>, AruidIndexMax>
controller_data{};
};
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 @@
#include "common/common_types.h"
#include "hid_core/hid_types.h"
+namespace Core::HID {
+class EmulatedController;
+}
+
namespace Service::HID {
static constexpr std::size_t MaxSupportedNpadIdTypes = 10;
static constexpr std::size_t StyleIndexCount = 7;
@@ -348,7 +352,7 @@ struct IAbstractedPad {
u8 indicator;
std::vector<f32> virtual_six_axis_sensor_acceleration;
std::vector<f32> virtual_six_axis_sensor_angle;
- u64 xcd_handle;
+ Core::HID::EmulatedController* xcd_handle;
u64 color;
};
} // 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() {
return ResultSuccess;
}
+u64 NpadVibration::GetSessionAruid() const {
+ return session_aruid;
+}
+
} // 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:
Result BeginPermitVibrationSession(u64 aruid);
Result EndPermitVibrationSession();
+ u64 GetSessionAruid() const;
+
private:
f32 volume{};
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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/npad/npad_vibration.h"
@@ -10,24 +11,25 @@ namespace Service::HID {
NpadGcVibrationDevice::NpadGcVibrationDevice() {}
-Result NpadGcVibrationDevice::IncrementRefCounter() {
+Result NpadGcVibrationDevice::Activate() {
if (ref_counter == 0 && is_mounted) {
f32 volume = 1.0f;
const auto result = vibration_handler->GetVibrationVolume(volume);
if (result.IsSuccess()) {
- // TODO: SendVibrationGcErmCommand
+ xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop);
}
}
+
ref_counter++;
return ResultSuccess;
}
-Result NpadGcVibrationDevice::DecrementRefCounter() {
- if (ref_counter == 1 && !is_mounted) {
+Result NpadGcVibrationDevice::Deactivate() {
+ if (ref_counter == 1 && is_mounted) {
f32 volume = 1.0f;
const auto result = vibration_handler->GetVibrationVolume(volume);
if (result.IsSuccess()) {
- // TODO: SendVibrationGcErmCommand
+ xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop);
}
}
@@ -38,6 +40,48 @@ Result NpadGcVibrationDevice::DecrementRefCounter() {
return ResultSuccess;
}
+Result NpadGcVibrationDevice::Mount(IAbstractedPad& abstracted_pad, u32 slot,
+ NpadVibration* handler) {
+ if (!abstracted_pad.internal_flags.is_connected) {
+ return ResultSuccess;
+ }
+
+ // TODO: This device doesn't use a xcd handle instead has an GC adapter handle. This is just to
+ // keep compatibility with the front end.
+ xcd_handle = abstracted_pad.xcd_handle;
+ adapter_slot = slot;
+ vibration_handler = handler;
+ is_mounted = true;
+
+ if (ref_counter == 0) {
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop);
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::Unmount() {
+ if (ref_counter == 0 || !is_mounted) {
+ is_mounted = false;
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(adapter_slot, Core::HID::VibrationGcErmCommand::Stop);
+ }
+
+ is_mounted = false;
+ return ResultSuccess;
+}
+
Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command) {
if (!is_mounted) {
return ResultSuccess;
@@ -55,7 +99,7 @@ Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcEr
return ResultSuccess;
}
}
- // TODO: SendVibrationGcErmCommand
+ xcd_handle->SetVibration(adapter_slot, command);
return ResultSuccess;
}
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 {
public:
explicit NpadGcVibrationDevice();
- Result IncrementRefCounter() override;
- Result DecrementRefCounter() override;
+ Result Activate() override;
+ Result Deactivate() override;
+
+ Result Mount(IAbstractedPad& abstracted_pad, u32 slot, NpadVibration* handler);
+ Result Unmount();
Result SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command);
Result GetActualVibrationGcErmCommand(Core::HID::VibrationGcErmCommand& out_command);
Result SendVibrationNotificationPattern(Core::HID::VibrationGcErmCommand command);
+
+private:
+ u32 adapter_slot;
};
} // 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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/npad/npad_vibration.h"
@@ -10,12 +11,12 @@ namespace Service::HID {
NpadN64VibrationDevice::NpadN64VibrationDevice() {}
-Result NpadN64VibrationDevice::IncrementRefCounter() {
+Result NpadN64VibrationDevice::Activate() {
if (ref_counter == 0 && is_mounted) {
f32 volume = 1.0f;
const auto result = vibration_handler->GetVibrationVolume(volume);
if (result.IsSuccess()) {
- // TODO: SendVibrationInBool
+ xcd_handle->SetVibration(false);
}
}
@@ -23,19 +24,12 @@ Result NpadN64VibrationDevice::IncrementRefCounter() {
return ResultSuccess;
}
-Result NpadN64VibrationDevice::DecrementRefCounter() {
- if (ref_counter == 1) {
- if (!is_mounted) {
- ref_counter = 0;
- if (is_mounted != false) {
- // TODO: SendVibrationInBool
- }
- return ResultSuccess;
- }
+Result NpadN64VibrationDevice::Deactivate() {
+ if (ref_counter == 1 && is_mounted) {
f32 volume = 1.0f;
const auto result = vibration_handler->GetVibrationVolume(volume);
if (result.IsSuccess()) {
- // TODO
+ xcd_handle->SetVibration(false);
}
}
@@ -46,6 +40,43 @@ Result NpadN64VibrationDevice::DecrementRefCounter() {
return ResultSuccess;
}
+Result NpadN64VibrationDevice::Mount(IAbstractedPad& abstracted_pad, NpadVibration* handler) {
+ if (!abstracted_pad.internal_flags.is_connected) {
+ return ResultSuccess;
+ }
+ xcd_handle = abstracted_pad.xcd_handle;
+ vibration_handler = handler;
+ is_mounted = true;
+
+ if (ref_counter == 0) {
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(false);
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::Unmount() {
+ if (ref_counter == 0 || !is_mounted) {
+ is_mounted = false;
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(false);
+ }
+
+ is_mounted = false;
+ return ResultSuccess;
+}
+
Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) {
if (ref_counter < 1) {
return ResultVibrationNotInitialized;
@@ -56,7 +87,7 @@ Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) {
if (result.IsError()) {
return result;
}
- // TODO: SendVibrationInBool
+ xcd_handle->SetVibration(false);
}
return ResultSuccess;
}
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 @@
namespace Service::HID {
class NpadVibration;
+struct IAbstractedPad;
/// Handles Npad request from HID interfaces
class NpadN64VibrationDevice final : public NpadVibrationBase {
public:
explicit NpadN64VibrationDevice();
- Result IncrementRefCounter() override;
- Result DecrementRefCounter() override;
+ Result Activate() override;
+ Result Deactivate() override;
+
+ Result Mount(IAbstractedPad& abstracted_pad, NpadVibration* handler);
+ Result Unmount();
Result SendValueInBool(bool is_vibrating);
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 {
NpadVibrationBase::NpadVibrationBase() {}
-Result NpadVibrationBase::IncrementRefCounter() {
+Result NpadVibrationBase::Activate() {
ref_counter++;
return ResultSuccess;
}
-Result NpadVibrationBase::DecrementRefCounter() {
+Result NpadVibrationBase::Deactivate() {
if (ref_counter > 0) {
ref_counter--;
}
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 @@
#include "common/common_types.h"
#include "core/hle/result.h"
+namespace Core::HID {
+class EmulatedController;
+}
+
namespace Service::HID {
class NpadVibration;
@@ -14,13 +18,13 @@ class NpadVibrationBase {
public:
explicit NpadVibrationBase();
- virtual Result IncrementRefCounter();
- virtual Result DecrementRefCounter();
+ virtual Result Activate();
+ virtual Result Deactivate();
bool IsVibrationMounted() const;
protected:
- u64 xcd_handle{};
+ Core::HID::EmulatedController* xcd_handle{nullptr};
s32 ref_counter{};
bool is_mounted{};
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 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/npad/npad_vibration.h"
@@ -10,12 +11,30 @@ namespace Service::HID {
NpadVibrationDevice::NpadVibrationDevice() {}
-Result NpadVibrationDevice::IncrementRefCounter() {
+Result NpadVibrationDevice::Activate() {
+ if (ref_counter == 0 && is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE);
+ // TODO: SendNotificationPattern;
+ }
+ }
+
ref_counter++;
return ResultSuccess;
}
-Result NpadVibrationDevice::DecrementRefCounter() {
+Result NpadVibrationDevice::Deactivate() {
+ if (ref_counter == 1 && is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE);
+ // TODO: SendNotificationPattern;
+ }
+ }
+
if (ref_counter > 0) {
ref_counter--;
}
@@ -23,6 +42,45 @@ Result NpadVibrationDevice::DecrementRefCounter() {
return ResultSuccess;
}
+Result NpadVibrationDevice::Mount(IAbstractedPad& abstracted_pad, Core::HID::DeviceIndex index,
+ NpadVibration* handler) {
+ if (!abstracted_pad.internal_flags.is_connected) {
+ return ResultSuccess;
+ }
+ xcd_handle = abstracted_pad.xcd_handle;
+ device_index = index;
+ vibration_handler = handler;
+ is_mounted = true;
+
+ if (ref_counter == 0) {
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(false);
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadVibrationDevice::Unmount() {
+ if (ref_counter == 0 || !is_mounted) {
+ is_mounted = false;
+ return ResultSuccess;
+ }
+
+ f32 volume{1.0f};
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE);
+ }
+
+ is_mounted = false;
+ return ResultSuccess;
+}
+
Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& value) {
if (ref_counter == 0) {
return ResultVibrationNotInitialized;
@@ -37,7 +95,7 @@ Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue&
return result;
}
if (volume <= 0.0f) {
- // TODO: SendVibrationValue
+ xcd_handle->SetVibration(device_index, Core::HID::DEFAULT_VIBRATION_VALUE);
return ResultSuccess;
}
@@ -45,7 +103,7 @@ Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue&
vibration_value.high_amplitude *= volume;
vibration_value.low_amplitude *= volume;
- // TODO: SendVibrationValue
+ xcd_handle->SetVibration(device_index, vibration_value);
return ResultSuccess;
}
@@ -63,11 +121,11 @@ Result NpadVibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u3
pattern = 0;
}
- // return xcd_handle->SendVibrationNotificationPattern(pattern);
+ // TODO: SendVibrationNotificationPattern;
return ResultSuccess;
}
-Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) {
+Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) const {
if (ref_counter < 1) {
return ResultVibrationNotInitialized;
}
@@ -77,7 +135,7 @@ Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& o
return ResultSuccess;
}
- // TODO: SendVibrationValue
+ out_value = xcd_handle->GetActualVibrationValue(device_index);
return ResultSuccess;
}
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 @@
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/vibration/vibration_base.h"
+namespace Core::HID {
+enum class DeviceIndex : u8;
+}
+
namespace Service::HID {
class NpadVibration;
@@ -20,16 +24,20 @@ class NpadVibrationDevice final : public NpadVibrationBase {
public:
explicit NpadVibrationDevice();
- Result IncrementRefCounter();
- Result DecrementRefCounter();
+ Result Activate();
+ Result Deactivate();
+
+ Result Mount(IAbstractedPad& abstracted_pad, Core::HID::DeviceIndex index,
+ NpadVibration* handler);
+ Result Unmount();
Result SendVibrationValue(const Core::HID::VibrationValue& value);
Result SendVibrationNotificationPattern(u32 pattern);
- Result GetActualVibrationValue(Core::HID::VibrationValue& out_value);
+ Result GetActualVibrationValue(Core::HID::VibrationValue& out_value) const;
private:
- u32 device_index{};
+ Core::HID::DeviceIndex device_index{};
};
} // 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() {
const auto& disabled = Settings::values.disabled_addons[title_id];
- for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
- const auto name =
- QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
+ for (const auto& patch : pm.GetPatches(update_raw)) {
+ const auto name = QString::fromStdString(patch.name);
auto* const first_item = new QStandardItem;
first_item->setText(name);
@@ -136,7 +135,7 @@ void ConfigurePerGameAddons::LoadConfiguration() {
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
list_items.push_back(QList<QStandardItem*>{
- first_item, new QStandardItem{QString::fromStdString(patch.second)}});
+ first_item, new QStandardItem{QString::fromStdString(patch.version)}});
item_model->appendRow(list_items.back());
}
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
.high_amplitude = 1.0f,
.high_frequency = 320.0f,
};
- controller->SetVibration(0, vibration);
- controller->SetVibration(1, vibration);
+ controller->SetVibration(Core::HID::DeviceIndex::Left, vibration);
+ controller->SetVibration(Core::HID::DeviceIndex::Right, vibration);
// Restore previous values
player.vibration_enabled = old_vibration_enabled;
@@ -127,7 +127,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type
void ConfigureVibration::StopVibrations() {
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
auto controller = hid_core.GetEmulatedControllerByIndex(i);
- controller->SetVibration(0, Core::HID::DEFAULT_VIBRATION_VALUE);
- controller->SetVibration(1, Core::HID::DEFAULT_VIBRATION_VALUE);
+ controller->SetVibration(Core::HID::DeviceIndex::Left, Core::HID::DEFAULT_VIBRATION_VALUE);
+ controller->SetVibration(Core::HID::DeviceIndex::Right, Core::HID::DEFAULT_VIBRATION_VALUE);
}
}
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,
QString out;
FileSys::VirtualFile update_raw;
loader.ReadUpdateRaw(update_raw);
- for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
- const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
+ for (const auto& patch : patch_manager.GetPatches(update_raw)) {
+ const bool is_update = patch.name == "Update";
if (!updatable && is_update) {
continue;
}
- const QString type = QString::fromStdString(kv.first);
+ const QString type =
+ QString::fromStdString(patch.enabled ? patch.name : "[D] " + patch.name);
- if (kv.second.empty()) {
+ if (patch.version.empty()) {
out.append(QStringLiteral("%1\n").arg(type));
} else {
- auto ver = kv.second;
+ auto ver = patch.version;
// Display container name for packed updates
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 @@
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/set/system_settings_server.h"
+#include "frontend_common/content_manager.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "yuzu/multiplayer/state.h"
@@ -2476,10 +2477,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
}
void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {
- const auto& fs_controller = system->GetFileSystemController();
- const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
- fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
-
+ const auto res =
+ ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id);
if (res) {
QMessageBox::information(this, tr("Successfully Removed"),
tr("Successfully removed the installed base game."));
@@ -2491,11 +2490,7 @@ void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {
}
void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
- const auto update_id = program_id | 0x800;
- const auto& fs_controller = system->GetFileSystemController();
- const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
- fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
-
+ const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
if (res) {
QMessageBox::information(this, tr("Successfully Removed"),
tr("Successfully removed the installed update."));
@@ -2506,22 +2501,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
}
void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) {
- u32 count{};
- const auto& fs_controller = system->GetFileSystemController();
- const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
- FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
-
- for (const auto& entry : dlc_entries) {
- if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
- const auto res =
- fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) ||
- fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id);
- if (res) {
- ++count;
- }
- }
- }
-
+ const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id);
if (count == 0) {
QMessageBox::warning(this, GetGameListErrorRemoving(type),
tr("There are no DLC installed for this title."));
@@ -3290,12 +3270,21 @@ void GMainWindow::OnMenuInstallToNAND() {
install_progress->setLabelText(
tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName()));
- QFuture<InstallResult> future;
- InstallResult result;
+ QFuture<ContentManager::InstallResult> future;
+ ContentManager::InstallResult result;
if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
-
- future = QtConcurrent::run([this, &file] { return InstallNSP(file); });
+ const auto progress_callback = [this](size_t size, size_t progress) {
+ emit UpdateInstallProgress();
+ if (install_progress->wasCanceled()) {
+ return true;
+ }
+ return false;
+ };
+ future = QtConcurrent::run([this, &file, progress_callback] {
+ return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(),
+ progress_callback);
+ });
while (!future.isFinished()) {
QCoreApplication::processEvents();
@@ -3311,16 +3300,16 @@ void GMainWindow::OnMenuInstallToNAND() {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
switch (result) {
- case InstallResult::Success:
+ case ContentManager::InstallResult::Success:
new_files.append(QFileInfo(file).fileName());
break;
- case InstallResult::Overwrite:
+ case ContentManager::InstallResult::Overwrite:
overwritten_files.append(QFileInfo(file).fileName());
break;
- case InstallResult::Failure:
+ case ContentManager::InstallResult::Failure:
failed_files.append(QFileInfo(file).fileName());
break;
- case InstallResult::BaseInstallAttempted:
+ case ContentManager::InstallResult::BaseInstallAttempted:
failed_files.append(QFileInfo(file).fileName());
detected_base_install = true;
break;
@@ -3354,96 +3343,7 @@ void GMainWindow::OnMenuInstallToNAND() {
ui->action_Install_File_NAND->setEnabled(true);
}
-InstallResult GMainWindow::InstallNSP(const QString& filename) {
- const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
- const FileSys::VirtualFile& dest, std::size_t block_size) {
- if (src == nullptr || dest == nullptr) {
- return false;
- }
- if (!dest->Resize(src->GetSize())) {
- return false;
- }
-
- std::vector<u8> buffer(CopyBufferSize);
-
- for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- if (install_progress->wasCanceled()) {
- dest->Resize(0);
- return false;
- }
-
- emit UpdateInstallProgress();
-
- const auto read = src->Read(buffer.data(), buffer.size(), i);
- dest->Write(buffer.data(), read, i);
- }
- return true;
- };
-
- std::shared_ptr<FileSys::NSP> nsp;
- if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- nsp = std::make_shared<FileSys::NSP>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- if (nsp->IsExtractedType()) {
- return InstallResult::Failure;
- }
- } else {
- return InstallResult::Failure;
- }
-
- if (nsp->GetStatus() != Loader::ResultStatus::Success) {
- return InstallResult::Failure;
- }
- const auto res = system->GetFileSystemController().GetUserNANDContents()->InstallEntry(
- *nsp, true, qt_raw_copy);
- switch (res) {
- case FileSys::InstallResult::Success:
- return InstallResult::Success;
- case FileSys::InstallResult::OverwriteExisting:
- return InstallResult::Overwrite;
- case FileSys::InstallResult::ErrorBaseInstall:
- return InstallResult::BaseInstallAttempted;
- default:
- return InstallResult::Failure;
- }
-}
-
-InstallResult GMainWindow::InstallNCA(const QString& filename) {
- const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
- const FileSys::VirtualFile& dest, std::size_t block_size) {
- if (src == nullptr || dest == nullptr) {
- return false;
- }
- if (!dest->Resize(src->GetSize())) {
- return false;
- }
-
- std::vector<u8> buffer(CopyBufferSize);
-
- for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
- if (install_progress->wasCanceled()) {
- dest->Resize(0);
- return false;
- }
-
- emit UpdateInstallProgress();
-
- const auto read = src->Read(buffer.data(), buffer.size(), i);
- dest->Write(buffer.data(), read, i);
- }
- return true;
- };
-
- const auto nca =
- std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- const auto id = nca->GetStatus();
-
- // Game updates necessary are missing base RomFS
- if (id != Loader::ResultStatus::Success &&
- id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
- return InstallResult::Failure;
- }
-
+ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) {
const QStringList tt_options{tr("System Application"),
tr("System Archive"),
tr("System Application Update"),
@@ -3464,7 +3364,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) {
if (!ok || index == -1) {
QMessageBox::warning(this, tr("Failed to Install"),
tr("The title type you selected for the NCA is invalid."));
- return InstallResult::Failure;
+ return ContentManager::InstallResult::Failure;
}
// If index is equal to or past Game, add the jump in TitleType.
@@ -3478,15 +3378,15 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) {
auto* registered_cache = is_application ? fs_controller.GetUserNANDContents()
: fs_controller.GetSystemNANDContents();
- const auto res = registered_cache->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
- true, qt_raw_copy);
- if (res == FileSys::InstallResult::Success) {
- return InstallResult::Success;
- } else if (res == FileSys::InstallResult::OverwriteExisting) {
- return InstallResult::Overwrite;
- } else {
- return InstallResult::Failure;
- }
+ const auto progress_callback = [this](size_t size, size_t progress) {
+ emit UpdateInstallProgress();
+ if (install_progress->wasCanceled()) {
+ return true;
+ }
+ return false;
+ };
+ return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache,
+ static_cast<FileSys::TitleType>(index), progress_callback);
}
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 @@
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "configuration/qt_config.h"
+#include "frontend_common/content_manager.h"
#include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -124,13 +125,6 @@ enum class EmulatedDirectoryTarget {
SDMC,
};
-enum class InstallResult {
- Success,
- Overwrite,
- Failure,
- BaseInstallAttempted,
-};
-
enum class ReinitializeKeyBehavior {
NoWarning,
Warning,
@@ -427,8 +421,7 @@ private:
void RemoveCacheStorage(u64 program_id);
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type);
- InstallResult InstallNSP(const QString& filename);
- InstallResult InstallNCA(const QString& filename);
+ ContentManager::InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
std::string_view gpu_vendor = {});