summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/format/script.sh3
-rw-r--r--.github/workflows/verify.yml8
-rw-r--r--src/android/app/build.gradle.kts83
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt98
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt105
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt88
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt101
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt192
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt72
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt36
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt50
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt129
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt18
-rw-r--r--src/android/app/src/main/jni/native.cpp10
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_about.xml4
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml4
-rw-r--r--src/android/app/src/main/res/menu/menu_driver_manager.xml11
-rw-r--r--src/common/page_table.cpp34
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/core.cpp1
-rw-r--r--src/core/debugger/debugger.cpp39
-rw-r--r--src/core/debugger/gdbstub.cpp66
-rw-r--r--src/core/debugger/gdbstub.h15
-rw-r--r--src/core/file_sys/savedata_factory.cpp17
-rw-r--r--src/core/file_sys/savedata_factory.h10
-rw-r--r--src/core/frontend/applets/controller.cpp2
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.cpp4
-rw-r--r--src/core/hle/kernel/k_memory_block_manager.h4
-rw-r--r--src/core/hle/kernel/k_page_table_base.cpp34
-rw-r--r--src/core/hle/kernel/k_page_table_base.h1
-rw-r--r--src/core/hle/kernel/k_process.cpp6
-rw-r--r--src/core/hle/kernel/kernel.cpp31
-rw-r--r--src/core/hle/kernel/kernel.h6
-rw-r--r--src/core/hle/kernel/svc/svc_process.cpp8
-rw-r--r--src/core/hle/service/am/am.cpp7
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp227
-rw-r--r--src/core/hle/service/filesystem/filesystem.h59
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp55
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h6
-rw-r--r--src/core/hle/service/filesystem/romfs_controller.cpp37
-rw-r--r--src/core/hle/service/filesystem/romfs_controller.h31
-rw-r--r--src/core/hle/service/filesystem/save_data_controller.cpp99
-rw-r--r--src/core/hle/service/filesystem/save_data_controller.h35
-rw-r--r--src/core/hle/service/glue/arp.cpp7
-rw-r--r--src/core/hle/service/hid/hid.cpp10
-rw-r--r--src/core/hle/service/hid/hid_server.cpp2
-rw-r--r--src/core/hle/service/pm/pm.cpp85
-rw-r--r--src/core/hle/service/server_manager.cpp9
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp14
-rw-r--r--src/core/loader/nca.cpp6
-rw-r--r--src/core/loader/nro.cpp10
-rw-r--r--src/core/loader/nsp.cpp3
-rw-r--r--src/core/loader/xci.cpp3
-rw-r--r--src/hid_core/CMakeLists.txt34
-rw-r--r--src/hid_core/frontend/emulated_controller.cpp22
-rw-r--r--src/hid_core/hid_types.h3
-rw-r--r--src/hid_core/hid_util.h2
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp197
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_battery_handler.h49
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp199
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_button_handler.h75
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp126
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h56
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp123
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_led_handler.h43
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp108
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h52
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp140
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h57
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad.cpp294
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad.h123
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp99
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_pad_holder.h47
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp47
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_palma_handler.h37
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp322
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_properties_handler.h86
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp154
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h61
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp73
-rw-r--r--src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h51
-rw-r--r--src/hid_core/resources/npad/npad.cpp8
-rw-r--r--src/hid_core/resources/npad/npad_data.cpp2
-rw-r--r--src/hid_core/resources/npad/npad_types.h99
-rw-r--r--src/hid_core/resources/npad/npad_vibration.cpp80
-rw-r--r--src/hid_core/resources/npad/npad_vibration.h34
-rw-r--r--src/hid_core/resources/six_axis/six_axis.cpp6
-rw-r--r--src/hid_core/resources/vibration/gc_vibration_device.cpp106
-rw-r--r--src/hid_core/resources/vibration/gc_vibration_device.h31
-rw-r--r--src/hid_core/resources/vibration/n64_vibration_device.cpp80
-rw-r--r--src/hid_core/resources/vibration/n64_vibration_device.h29
-rw-r--r--src/hid_core/resources/vibration/vibration_base.cpp30
-rw-r--r--src/hid_core/resources/vibration/vibration_base.h28
-rw-r--r--src/hid_core/resources/vibration/vibration_device.cpp84
-rw-r--r--src/hid_core/resources/vibration/vibration_device.h35
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp40
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp51
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h3
-rw-r--r--src/yuzu/applets/qt_controller.cpp8
-rw-r--r--src/yuzu/applets/qt_software_keyboard.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp18
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp2
-rw-r--r--src/yuzu/main.cpp10
-rw-r--r--src/yuzu/util/controller_navigation.cpp4
116 files changed, 4707 insertions, 1119 deletions
diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index c22398de0..f9c63dbfa 100755
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -32,3 +32,6 @@ if [ ! -z "$DIFF" ]; then
echo "$DIFF"
exit 1
fi
+
+cd src/android
+./gradlew ktlintCheck
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index c073f3f3f..62eb69aeb 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -13,13 +13,15 @@ jobs:
format:
name: 'verify format'
runs-on: ubuntu-latest
- container:
- image: yuzuemu/build-environments:linux-clang-format
- options: -u 1001
steps:
- uses: actions/checkout@v3
with:
submodules: false
+ - name: set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
- name: 'Verify Formatting'
run: bash -ex ./.ci/scripts/format/script.sh
build:
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 53aafa08c..06e59d1ac 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -188,8 +188,15 @@ tasks.create<Delete>("ktlintReset") {
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
}
+val showFormatHelp = {
+ logger.lifecycle(
+ "If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
+ "codestyle fixes"
+ )
+}
+tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
+tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
-tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
version.set("0.47.1")
@@ -228,71 +235,33 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
-fun getGitVersion(): String {
- var versionName = "0.0"
-
- try {
- versionName = ProcessBuilder("git", "describe", "--always", "--long")
+fun runGitCommand(command: List<String>): String {
+ return try {
+ ProcessBuilder(command)
.directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start().inputStream.bufferedReader().use { it.readText() }
.trim()
- .replace(Regex("(-0)?-[^-]+$"), "")
} catch (e: Exception) {
- logger.error("Cannot find git, defaulting to dummy version number")
+ logger.error("Cannot find git")
+ ""
}
-
- if (System.getenv("GITHUB_ACTIONS") != null) {
- val gitTag = System.getenv("GIT_TAG_NAME")
- versionName = gitTag ?: versionName
- }
-
- return versionName
}
-fun getGitHash(): String {
- try {
- val processBuilder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
- processBuilder.directory(project.rootDir)
- val process = processBuilder.start()
- val inputStream = process.inputStream
- val errorStream = process.errorStream
- process.waitFor()
-
- return if (process.exitValue() == 0) {
- inputStream.bufferedReader()
- .use { it.readText().trim() } // return the value of gitHash
- } else {
- val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
- logger.error("Error running git command: $errorMessage")
- "dummy-hash" // return a dummy hash value in case of an error
- }
- } catch (e: Exception) {
- logger.error("$e: Cannot find git, defaulting to dummy build hash")
- return "dummy-hash" // return a dummy hash value in case of an error
+fun getGitVersion(): String {
+ val versionName = if (System.getenv("GITHUB_ACTIONS") != null) {
+ val gitTag = System.getenv("GIT_TAG_NAME") ?: ""
+ gitTag
+ } else {
+ runGitCommand(listOf("git", "describe", "--always", "--long"))
+ .replace(Regex("(-0)?-[^-]+$"), "")
}
+ return versionName.ifEmpty { "0.0" }
}
-fun getBranch(): String {
- try {
- val processBuilder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
- processBuilder.directory(project.rootDir)
- val process = processBuilder.start()
- val inputStream = process.inputStream
- val errorStream = process.errorStream
- process.waitFor()
-
- return if (process.exitValue() == 0) {
- inputStream.bufferedReader()
- .use { it.readText().trim() } // return the value of gitHash
- } else {
- val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
- logger.error("Error running git command: $errorMessage")
- "dummy-hash" // return a dummy hash value in case of an error
- }
- } catch (e: Exception) {
- logger.error("$e: Cannot find git, defaulting to dummy build hash")
- return "dummy-hash" // return a dummy hash value in case of an error
- }
-}
+fun getGitHash(): String =
+ runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
+
+fun getBranch(): String =
+ runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
new file mode 100644
index 000000000..f006f9e3d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate
+ * code used in every [RecyclerView].
+ * Type assigned to [Model] must inherit from [Object] in order to be compared properly.
+ */
+abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> :
+ ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) {
+ override fun onBindViewHolder(holder: Holder, position: Int) =
+ holder.bind(currentList[position])
+
+ private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() {
+ override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
+ return oldItem === newItem
+ }
+
+ @SuppressLint("DiffUtilEquals")
+ override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
+ return oldItem == newItem
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
new file mode 100644
index 000000000..3dfee3d0c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+
+/**
+ * Generic list class meant to take care of basic lists
+ * @param currentList The list to show initially
+ */
+abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
+ open var currentList: List<Model>
+) : RecyclerView.Adapter<Holder>() {
+ override fun onBindViewHolder(holder: Holder, position: Int) =
+ holder.bind(currentList[position])
+
+ override fun getItemCount(): Int = currentList.size
+
+ /**
+ * Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter
+ * is passed in for position, [item] is added to the end of the list. Invokes [callback] last.
+ * @param item The item to add to the list
+ * @param position Index where [item] will be added
+ * @param callback Lambda that's called at the end of the list changes and has the added list
+ * position passed in as a parameter
+ */
+ open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) {
+ val newList = currentList.toMutableList()
+ val positionToUpdate: Int
+ if (position == -1) {
+ newList.add(item)
+ currentList = newList
+ positionToUpdate = currentList.size - 1
+ } else {
+ newList.add(position, item)
+ currentList = newList
+ positionToUpdate = position
+ }
+ onItemAdded(positionToUpdate, callback)
+ }
+
+ protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) {
+ notifyItemInserted(position)
+ callback?.invoke(position)
+ }
+
+ /**
+ * Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter
+ * of the change. Invokes [callback] last.
+ * @param item New list item
+ * @param position Index where [item] will replace the existing list item
+ * @param callback Lambda that's called at the end of the list changes and has the changed list
+ * position passed in as a parameter
+ */
+ fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) {
+ val newList = currentList.toMutableList()
+ newList[position] = item
+ currentList = newList
+ onItemChanged(position, callback)
+ }
+
+ protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) {
+ notifyItemChanged(position)
+ callback?.invoke(position)
+ }
+
+ /**
+ * Removes the list item at [position] in [currentList] and notifies the underlying adapter
+ * of the change. Invokes [callback] last.
+ * @param position Index where the list item will be removed
+ * @param callback Lambda that's called at the end of the list changes and has the removed list
+ * position passed in as a parameter
+ */
+ fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
+ val newList = currentList.toMutableList()
+ newList.removeAt(position)
+ currentList = newList
+ onItemRemoved(position, callback)
+ }
+
+ protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) {
+ notifyItemRemoved(position)
+ callback?.invoke(position)
+ }
+
+ /**
+ * Replaces [currentList] with [newList] and notifies the underlying adapter of the change.
+ * @param newList The new list to replace [currentList]
+ */
+ @SuppressLint("NotifyDataSetChanged")
+ open fun replaceList(newList: List<Model>) {
+ currentList = newList
+ notifyDataSetChanged()
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
new file mode 100644
index 000000000..52163f9d7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import org.yuzu.yuzu_emu.model.SelectableItem
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+
+/**
+ * Generic list class meant to take care of single selection UI updates
+ * @param currentList The list to show initially
+ * @param defaultSelection The default selection to use if no list items are selected by
+ * [SelectableItem.selected] or if the currently selected item is removed from the list
+ */
+abstract class AbstractSingleSelectionList<
+ Model : SelectableItem,
+ Holder : AbstractViewHolder<Model>
+ >(
+ final override var currentList: List<Model>,
+ private val defaultSelection: DefaultSelection = DefaultSelection.Start
+) : AbstractListAdapter<Model, Holder>(currentList) {
+ var selectedItem = getDefaultSelection()
+
+ init {
+ findSelectedItem()
+ }
+
+ /**
+ * Changes the selection state of the [SelectableItem] that was selected and the previously selected
+ * item and notifies the underlying adapter of the change for those items. Invokes [callback] last.
+ * Does nothing if [position] is the same as the currently selected item.
+ * @param position Index of the item that was selected
+ * @param callback Lambda that's called at the end of the list changes and has the selected list
+ * position passed in as a parameter
+ */
+ fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
+ if (position == selectedItem) {
+ return
+ }
+
+ val previouslySelectedItem = selectedItem
+ selectedItem = position
+ if (currentList.indices.contains(selectedItem)) {
+ currentList[selectedItem].onSelectionStateChanged(true)
+ }
+ if (currentList.indices.contains(previouslySelectedItem)) {
+ currentList[previouslySelectedItem].onSelectionStateChanged(false)
+ }
+ onItemChanged(previouslySelectedItem)
+ onItemChanged(selectedItem)
+ callback?.invoke(position)
+ }
+
+ /**
+ * Removes a given item from the list and notifies the underlying adapter of the change. If the
+ * currently selected item was the item that was removed, the item at the position provided
+ * by [defaultSelection] will be made the new selection. Invokes [callback] last.
+ * @param position Index of the item that was removed
+ * @param callback Lambda that's called at the end of the list changes and has the removed and
+ * selected list positions passed in as parameters
+ */
+ fun removeSelectableItem(
+ position: Int,
+ callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)?
+ ) {
+ removeItem(position)
+ if (position == selectedItem) {
+ selectedItem = getDefaultSelection()
+ currentList[selectedItem].onSelectionStateChanged(true)
+ onItemChanged(selectedItem)
+ } else if (position < selectedItem) {
+ selectedItem--
+ }
+ callback?.invoke(position, selectedItem)
+ }
+
+ override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) {
+ super.addItem(item, position, callback)
+ if (position <= selectedItem && position != -1) {
+ selectedItem++
+ }
+ }
+
+ override fun replaceList(newList: List<Model>) {
+ super.replaceList(newList)
+ findSelectedItem()
+ }
+
+ private fun findSelectedItem() {
+ for (i in currentList.indices) {
+ if (currentList[i].selected) {
+ selectedItem = i
+ break
+ }
+ }
+ }
+
+ private fun getDefaultSelection(): Int =
+ when (defaultSelection) {
+ DefaultSelection.Start -> currentList.indices.first
+ DefaultSelection.End -> currentList.indices.last
+ }
+
+ enum class DefaultSelection { Start, End }
+}
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 15c7ca3c9..94c151325 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
@@ -5,48 +5,28 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Addon
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
- AsyncDifferConfig.Builder(DiffCallback()).build()
-) {
+class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) }
}
- override fun getItemCount(): Int = currentList.size
-
- override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
- holder.bind(currentList[position])
-
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
- RecyclerView.ViewHolder(binding.root) {
- fun bind(addon: Addon) {
+ AbstractViewHolder<Addon>(binding) {
+ override fun bind(model: Addon) {
binding.root.setOnClickListener {
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
}
- binding.title.text = addon.title
- binding.version.text = addon.version
+ binding.title.text = model.title
+ binding.version.text = model.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
- addon.enabled = checked
+ model.enabled = checked
}
- binding.addonSwitch.isChecked = addon.enabled
- }
- }
-
- private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
- override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
- return oldItem == newItem
- }
-
- override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
- return oldItem == newItem
+ binding.addonSwitch.isChecked = model.enabled
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
index 4a05c5be9..41d7f72b8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
@@ -4,13 +4,11 @@
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController
-import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -19,72 +17,58 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
- RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
- View.OnClickListener {
-
+class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
+ AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AppletAdapter.AppletViewHolder {
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- .apply { root.setOnClickListener(this@AppletAdapter) }
.also { return AppletViewHolder(it) }
}
- override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
- holder.bind(applets[position])
-
- override fun getItemCount(): Int = applets.size
-
- override fun onClick(view: View) {
- val applet = (view.tag as AppletViewHolder).applet
- val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
- if (appletPath.isEmpty()) {
- Toast.makeText(
- YuzuApplication.appContext,
- R.string.applets_error_applet,
- Toast.LENGTH_SHORT
- ).show()
- return
- }
-
- if (applet.appletInfo == AppletInfo.Cabinet) {
- view.findNavController()
- .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
- return
- }
-
- NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
- val appletGame = Game(
- title = YuzuApplication.appContext.getString(applet.titleId),
- path = appletPath
- )
- val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
- view.findNavController().navigate(action)
- }
-
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
- RecyclerView.ViewHolder(binding.root) {
- lateinit var applet: Applet
-
- init {
- itemView.tag = this
- }
-
- fun bind(applet: Applet) {
- this.applet = applet
-
- binding.title.setText(applet.titleId)
- binding.description.setText(applet.descriptionId)
+ AbstractViewHolder<Applet>(binding) {
+ override fun bind(model: Applet) {
+ binding.title.setText(model.titleId)
+ binding.description.setText(model.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
- applet.iconId,
+ model.iconId,
binding.icon.context.theme
)
)
+
+ binding.root.setOnClickListener { onClick(model) }
+ }
+
+ fun onClick(applet: Applet) {
+ val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
+ if (appletPath.isEmpty()) {
+ Toast.makeText(
+ binding.root.context,
+ R.string.applets_error_applet,
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
+
+ if (applet.appletInfo == AppletInfo.Cabinet) {
+ binding.root.findNavController()
+ .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
+ return
+ }
+
+ NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
+ val appletGame = Game(
+ title = YuzuApplication.appContext.getString(applet.titleId),
+ path = appletPath
+ )
+ val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+ binding.root.findNavController().navigate(action)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
index e7b7c0f2f..a56137148 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
@@ -4,12 +4,10 @@
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -19,54 +17,43 @@ import org.yuzu.yuzu_emu.model.CabinetMode
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class CabinetLauncherDialogAdapter(val fragment: Fragment) :
- RecyclerView.Adapter<CabinetModeViewHolder>(),
- View.OnClickListener {
- private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
+ AbstractListAdapter<CabinetMode, CabinetModeViewHolder>(
+ CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList()
+ ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
.also { return CabinetModeViewHolder(it) }
}
- override fun getItemCount(): Int = cabinetModes.size
-
- override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
- holder.bind(cabinetModes[position])
-
- override fun onClick(view: View) {
- val mode = (view.tag as CabinetModeViewHolder).cabinetMode
- val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
- NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
- NativeLibrary.setCabinetMode(mode.id)
- val appletGame = Game(
- title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
- path = appletPath
- )
- val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
- fragment.findNavController().navigate(action)
- }
-
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
- RecyclerView.ViewHolder(binding.root) {
- lateinit var cabinetMode: CabinetMode
-
- init {
- itemView.tag = this
- }
-
- fun bind(cabinetMode: CabinetMode) {
- this.cabinetMode = cabinetMode
+ AbstractViewHolder<CabinetMode>(binding) {
+ override fun bind(model: CabinetMode) {
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
- cabinetMode.iconId,
+ model.iconId,
binding.icon.context.theme
)
)
- binding.title.setText(cabinetMode.titleId)
+ binding.title.setText(model.titleId)
+
+ binding.root.setOnClickListener { onClick(model) }
+ }
+
+ private fun onClick(mode: CabinetMode) {
+ val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
+ NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
+ NativeLibrary.setCabinetMode(mode.id)
+ val appletGame = Game(
+ title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
+ path = appletPath
+ )
+ val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+ fragment.findNavController().navigate(action)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
index d290a656c..d6f17cf29 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -7,65 +7,39 @@ import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
+import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.model.Driver
import org.yuzu.yuzu_emu.model.DriverViewModel
-import org.yuzu.yuzu_emu.utils.GpuDriverHelper
-import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class DriverAdapter(private val driverViewModel: DriverViewModel) :
- ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
- AsyncDifferConfig.Builder(DiffCallback()).build()
+ AbstractSingleSelectionList<Driver, DriverAdapter.DriverViewHolder>(
+ driverViewModel.driverList.value
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
- val binding =
- CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return DriverViewHolder(binding)
- }
-
- override fun getItemCount(): Int = currentList.size
-
- override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
- holder.bind(currentList[position])
-
- private fun onSelectDriver(position: Int) {
- driverViewModel.setSelectedDriverIndex(position)
- notifyItemChanged(driverViewModel.previouslySelectedDriver)
- notifyItemChanged(driverViewModel.selectedDriver)
- }
-
- private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
- if (driverViewModel.selectedDriver > position) {
- driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
- }
- if (GpuDriverHelper.customDriverSettingData == driverData.second) {
- driverViewModel.setSelectedDriverIndex(0)
- }
- driverViewModel.driversToDelete.add(driverData.first)
- driverViewModel.removeDriver(driverData)
- notifyItemRemoved(position)
- notifyItemChanged(driverViewModel.selectedDriver)
+ CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return DriverViewHolder(it) }
}
inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
- RecyclerView.ViewHolder(binding.root) {
- private lateinit var driverData: Pair<String, GpuDriverMetadata>
-
- fun bind(driverData: Pair<String, GpuDriverMetadata>) {
- this.driverData = driverData
- val driver = driverData.second
-
+ AbstractViewHolder<Driver>(binding) {
+ override fun bind(model: Driver) {
binding.apply {
- radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
+ radioButton.isChecked = model.selected
root.setOnClickListener {
- onSelectDriver(bindingAdapterPosition)
+ selectItem(bindingAdapterPosition) {
+ driverViewModel.onDriverSelected(it)
+ driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+ }
}
buttonDelete.setOnClickListener {
- onDeleteDriver(driverData, bindingAdapterPosition)
+ removeSelectableItem(
+ bindingAdapterPosition
+ ) { removedPosition: Int, selectedPosition: Int ->
+ driverViewModel.onDriverRemoved(removedPosition, selectedPosition)
+ driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+ }
}
// Delay marquee by 3s
@@ -80,38 +54,19 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
},
3000
)
- if (driver.name == null) {
- title.setText(R.string.system_gpu_driver)
- description.text = ""
- version.text = ""
- version.visibility = View.GONE
- description.visibility = View.GONE
- buttonDelete.visibility = View.GONE
- } else {
- title.text = driver.name
- version.text = driver.version
- description.text = driver.description
+ title.text = model.title
+ version.text = model.version
+ description.text = model.description
+ if (model.description.isNotEmpty()) {
version.visibility = View.VISIBLE
description.visibility = View.VISIBLE
buttonDelete.visibility = View.VISIBLE
+ } else {
+ version.visibility = View.GONE
+ description.visibility = View.GONE
+ buttonDelete.visibility = View.GONE
}
}
}
}
-
- private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
- override fun areItemsTheSame(
- oldItem: Pair<String, GpuDriverMetadata>,
- newItem: Pair<String, GpuDriverMetadata>
- ): Boolean {
- return oldItem.first == newItem.first
- }
-
- override fun areContentsTheSame(
- oldItem: Pair<String, GpuDriverMetadata>,
- newItem: Pair<String, GpuDriverMetadata>
- ): Boolean {
- return oldItem.second == newItem.second
- }
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
index ab657a7b9..3d8f0bda8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -8,19 +8,14 @@ import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
- ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
- AsyncDifferConfig.Builder(DiffCallback()).build()
- ) {
+ AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
@@ -29,18 +24,11 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
.also { return FolderViewHolder(it) }
}
- override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
- holder.bind(currentList[position])
-
inner class FolderViewHolder(val binding: CardFolderBinding) :
- RecyclerView.ViewHolder(binding.root) {
- private lateinit var gameDir: GameDir
-
- fun bind(gameDir: GameDir) {
- this.gameDir = gameDir
-
+ AbstractViewHolder<GameDir>(binding) {
+ override fun bind(model: GameDir) {
binding.apply {
- path.text = Uri.parse(gameDir.uriString).path
+ path.text = Uri.parse(model.uriString).path
path.postDelayed(
{
path.isSelected = true
@@ -50,7 +38,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
)
buttonEdit.setOnClickListener {
- GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
+ GameFolderPropertiesDialogFragment.newInstance(model)
.show(
activity.supportFragmentManager,
GameFolderPropertiesDialogFragment.TAG
@@ -58,19 +46,9 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
}
buttonDelete.setOnClickListener {
- gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
+ gamesViewModel.removeFolder(model)
}
}
}
}
-
- private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
- override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
- return oldItem == newItem
- }
-
- override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
- return oldItem == newItem
- }
- }
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index a578f0de8..e26c2e0ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -9,7 +9,6 @@ import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
-import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
@@ -25,10 +24,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -36,122 +31,26 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity
-import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GameAdapter(private val activity: AppCompatActivity) :
- ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
- View.OnClickListener,
- View.OnLongClickListener {
+ AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
- // Create a new view.
- val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- binding.cardGame.setOnClickListener(this)
- binding.cardGame.setOnLongClickListener(this)
-
- // Use that view to create a ViewHolder.
- return GameViewHolder(binding)
- }
-
- override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
- holder.bind(currentList[position])
-
- override fun getItemCount(): Int = currentList.size
-
- /**
- * Launches the game that was clicked on.
- *
- * @param view The card representing the game the user wants to play.
- */
- override fun onClick(view: View) {
- val holder = view.tag as GameViewHolder
-
- val gameExists = DocumentFile.fromSingleUri(
- YuzuApplication.appContext,
- Uri.parse(holder.game.path)
- )?.exists() == true
- if (!gameExists) {
- Toast.makeText(
- YuzuApplication.appContext,
- R.string.loader_error_file_not_found,
- Toast.LENGTH_LONG
- ).show()
-
- ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
- return
- }
-
- val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- preferences.edit()
- .putLong(
- holder.game.keyLastPlayedTime,
- System.currentTimeMillis()
- )
- .apply()
-
- val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
- action = Intent.ACTION_VIEW
- data = Uri.parse(holder.game.path)
- }
-
- activity.lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- val layerDrawable = ResourcesCompat.getDrawable(
- YuzuApplication.appContext.resources,
- R.drawable.shortcut,
- null
- ) as LayerDrawable
- layerDrawable.setDrawableByLayerId(
- R.id.shortcut_foreground,
- GameIconUtils.getGameIcon(activity, holder.game)
- .toDrawable(YuzuApplication.appContext.resources)
- )
- val inset = YuzuApplication.appContext.resources
- .getDimensionPixelSize(R.dimen.icon_inset)
- layerDrawable.setLayerInset(1, inset, inset, inset, inset)
- val shortcut =
- ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
- .setShortLabel(holder.game.title)
- .setIcon(
- IconCompat.createWithAdaptiveBitmap(
- layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
- )
- )
- .setIntent(openIntent)
- .build()
- ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
- }
- }
-
- val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
- view.findNavController().navigate(action)
- }
-
- override fun onLongClick(view: View): Boolean {
- val holder = view.tag as GameViewHolder
- val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
- view.findNavController().navigate(action)
- return true
+ CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return GameViewHolder(it) }
}
inner class GameViewHolder(val binding: CardGameBinding) :
- RecyclerView.ViewHolder(binding.root) {
- lateinit var game: Game
-
- init {
- binding.cardGame.tag = this
- }
-
- fun bind(game: Game) {
- this.game = game
-
+ AbstractViewHolder<Game>(binding) {
+ override fun bind(model: Game) {
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
- GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
+ GameIconUtils.loadGameIcon(model, binding.imageGameScreen)
- binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
+ binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
binding.textGameTitle.postDelayed(
{
@@ -160,16 +59,79 @@ class GameAdapter(private val activity: AppCompatActivity) :
},
3000
)
+
+ binding.cardGame.setOnClickListener { onClick(model) }
+ binding.cardGame.setOnLongClickListener { onLongClick(model) }
}
- }
- private class DiffCallback : DiffUtil.ItemCallback<Game>() {
- override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
- return oldItem == newItem
+ fun onClick(game: Game) {
+ val gameExists = DocumentFile.fromSingleUri(
+ YuzuApplication.appContext,
+ Uri.parse(game.path)
+ )?.exists() == true
+ if (!gameExists) {
+ Toast.makeText(
+ YuzuApplication.appContext,
+ R.string.loader_error_file_not_found,
+ Toast.LENGTH_LONG
+ ).show()
+
+ ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
+ return
+ }
+
+ val preferences =
+ PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+ preferences.edit()
+ .putLong(
+ game.keyLastPlayedTime,
+ System.currentTimeMillis()
+ )
+ .apply()
+
+ val openIntent =
+ Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ data = Uri.parse(game.path)
+ }
+
+ activity.lifecycleScope.launch {
+ withContext(Dispatchers.IO) {
+ val layerDrawable = ResourcesCompat.getDrawable(
+ YuzuApplication.appContext.resources,
+ R.drawable.shortcut,
+ null
+ ) as LayerDrawable
+ layerDrawable.setDrawableByLayerId(
+ R.id.shortcut_foreground,
+ GameIconUtils.getGameIcon(activity, game)
+ .toDrawable(YuzuApplication.appContext.resources)
+ )
+ val inset = YuzuApplication.appContext.resources
+ .getDimensionPixelSize(R.dimen.icon_inset)
+ layerDrawable.setLayerInset(1, inset, inset, inset, inset)
+ val shortcut =
+ ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path)
+ .setShortLabel(game.title)
+ .setIcon(
+ IconCompat.createWithAdaptiveBitmap(
+ layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
+ )
+ )
+ .setIntent(openIntent)
+ .build()
+ ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+ }
+ }
+
+ val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true)
+ binding.root.findNavController().navigate(action)
}
- override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
- return oldItem == newItem
+ fun onLongClick(game: Game): Boolean {
+ val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game)
+ binding.root.findNavController().navigate(action)
+ return true
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
index 95841d786..0046d5314 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
@@ -12,23 +12,22 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GamePropertiesAdapter(
private val viewLifecycle: LifecycleOwner,
private var properties: List<GameProperty>
-) :
- RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
+) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
- ): GamePropertyViewHolder {
+ ): AbstractViewHolder<GameProperty> {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
PropertyType.Submenu.ordinal -> {
@@ -51,11 +50,6 @@ class GamePropertiesAdapter(
}
}
- override fun getItemCount(): Int = properties.size
-
- override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
- holder.bind(properties[position])
-
override fun getItemViewType(position: Int): Int {
return when (properties[position]) {
is SubmenuProperty -> PropertyType.Submenu.ordinal
@@ -63,14 +57,10 @@ class GamePropertiesAdapter(
}
}
- sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- abstract fun bind(property: GameProperty)
- }
-
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
- GamePropertyViewHolder(binding.root) {
- override fun bind(property: GameProperty) {
- val submenuProperty = property as SubmenuProperty
+ AbstractViewHolder<GameProperty>(binding) {
+ override fun bind(model: GameProperty) {
+ val submenuProperty = model as SubmenuProperty
binding.root.setOnClickListener {
submenuProperty.action.invoke()
@@ -108,9 +98,9 @@ class GamePropertiesAdapter(
}
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
- GamePropertyViewHolder(binding.root) {
- override fun bind(property: GameProperty) {
- val installableProperty = property as InstallableProperty
+ AbstractViewHolder<GameProperty>(binding) {
+ override fun bind(model: GameProperty) {
+ val installableProperty = model as InstallableProperty
binding.title.setText(installableProperty.titleId)
binding.description.setText(installableProperty.descriptionId)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 58ce343f4..b512845d5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -14,69 +14,37 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class HomeSettingAdapter(
private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner,
- var options: List<HomeSetting>
-) :
- RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
- View.OnClickListener {
+ options: List<HomeSetting>
+) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
- val binding =
- CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- binding.root.setOnClickListener(this)
- return HomeOptionViewHolder(binding)
- }
-
- override fun getItemCount(): Int {
- return options.size
- }
-
- override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
- holder.bind(options[position])
- }
-
- override fun onClick(view: View) {
- val holder = view.tag as HomeOptionViewHolder
- if (holder.option.isEnabled.invoke()) {
- holder.option.onClick.invoke()
- } else {
- MessageDialogFragment.newInstance(
- activity,
- titleId = holder.option.disabledTitleId,
- descriptionId = holder.option.disabledMessageId
- ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
- }
+ CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return HomeOptionViewHolder(it) }
}
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
- RecyclerView.ViewHolder(binding.root) {
- lateinit var option: HomeSetting
-
- init {
- itemView.tag = this
- }
-
- fun bind(option: HomeSetting) {
- this.option = option
- binding.optionTitle.text = activity.resources.getString(option.titleId)
- binding.optionDescription.text = activity.resources.getString(option.descriptionId)
+ AbstractViewHolder<HomeSetting>(binding) {
+ override fun bind(model: HomeSetting) {
+ binding.optionTitle.text = activity.resources.getString(model.titleId)
+ binding.optionDescription.text = activity.resources.getString(model.descriptionId)
binding.optionIcon.setImageDrawable(
ResourcesCompat.getDrawable(
activity.resources,
- option.iconId,
+ model.iconId,
activity.theme
)
)
- when (option.titleId) {
+ when (model.titleId) {
R.string.get_early_access ->
binding.optionLayout.background =
ContextCompat.getDrawable(
@@ -85,7 +53,7 @@ class HomeSettingAdapter(
)
}
- if (!option.isEnabled.invoke()) {
+ if (!model.isEnabled.invoke()) {
binding.optionTitle.alpha = 0.5f
binding.optionDescription.alpha = 0.5f
binding.optionIcon.alpha = 0.5f
@@ -93,7 +61,7 @@ class HomeSettingAdapter(
viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
- option.details.collect { updateOptionDetails(it) }
+ model.details.collect { updateOptionDetails(it) }
}
}
binding.optionDetail.postDelayed(
@@ -103,6 +71,20 @@ class HomeSettingAdapter(
},
3000
)
+
+ binding.root.setOnClickListener { onClick(model) }
+ }
+
+ private fun onClick(model: HomeSetting) {
+ if (model.isEnabled.invoke()) {
+ model.onClick.invoke()
+ } else {
+ MessageDialogFragment.newInstance(
+ activity,
+ titleId = model.disabledTitleId,
+ descriptionId = model.disabledMessageId
+ ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
+ }
}
private fun updateOptionDetails(detailString: String) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
index e960fbaab..4218c4e52 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -6,43 +6,33 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
import org.yuzu.yuzu_emu.model.Installable
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class InstallableAdapter(private val installables: List<Installable>) :
- RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
+class InstallableAdapter(installables: List<Installable>) :
+ AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): InstallableAdapter.InstallableViewHolder {
- val binding =
- CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return InstallableViewHolder(binding)
+ CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return InstallableViewHolder(it) }
}
- override fun getItemCount(): Int = installables.size
-
- override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
- holder.bind(installables[position])
-
inner class InstallableViewHolder(val binding: CardInstallableBinding) :
- RecyclerView.ViewHolder(binding.root) {
- lateinit var installable: Installable
-
- fun bind(installable: Installable) {
- this.installable = installable
-
- binding.title.setText(installable.titleId)
- binding.description.setText(installable.descriptionId)
+ AbstractViewHolder<Installable>(binding) {
+ override fun bind(model: Installable) {
+ binding.title.setText(model.titleId)
+ binding.description.setText(model.descriptionId)
- if (installable.install != null) {
+ if (model.install != null) {
binding.buttonInstall.visibility = View.VISIBLE
- binding.buttonInstall.setOnClickListener { installable.install.invoke() }
+ binding.buttonInstall.setOnClickListener { model.install.invoke() }
}
- if (installable.export != null) {
+ if (model.export != null) {
binding.buttonExport.visibility = View.VISIBLE
- binding.buttonExport.setOnClickListener { installable.export.invoke() }
+ binding.buttonExport.setOnClickListener { model.export.invoke() }
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index bc6ff1364..38bb1f96f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -7,49 +7,33 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
import org.yuzu.yuzu_emu.model.License
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
- RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
- View.OnClickListener {
+class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
+ AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
- val binding =
- ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- binding.root.setOnClickListener(this)
- return LicenseViewHolder(binding)
+ ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return LicenseViewHolder(it) }
}
- override fun getItemCount(): Int = licenses.size
+ inner class LicenseViewHolder(val binding: ListItemSettingBinding) :
+ AbstractViewHolder<License>(binding) {
+ override fun bind(model: License) {
+ binding.apply {
+ textSettingName.text = root.context.getString(model.titleId)
+ textSettingDescription.text = root.context.getString(model.descriptionId)
+ textSettingValue.visibility = View.GONE
- override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
- holder.bind(licenses[position])
- }
-
- override fun onClick(view: View) {
- val license = (view.tag as LicenseViewHolder).license
- LicenseBottomSheetDialogFragment.newInstance(license)
- .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
- }
-
- inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
- lateinit var license: License
-
- init {
- itemView.tag = this
+ root.setOnClickListener { onClick(model) }
+ }
}
- fun bind(license: License) {
- this.license = license
-
- val context = YuzuApplication.appContext
- binding.textSettingName.text = context.getString(license.titleId)
- binding.textSettingDescription.text = context.getString(license.descriptionId)
- binding.textSettingValue.visibility = View.GONE
+ private fun onClick(license: License) {
+ LicenseBottomSheetDialogFragment.newInstance(license)
+ .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 6b46d359e..02118e1a8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -10,7 +10,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.databinding.PageSetupBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -18,31 +17,19 @@ import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
-class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
- RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
+class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
+ AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
- val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return SetupPageViewHolder(binding)
+ PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return SetupPageViewHolder(it) }
}
- override fun getItemCount(): Int = pages.size
-
- override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
- holder.bind(pages[position])
-
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
- RecyclerView.ViewHolder(binding.root), SetupCallback {
- lateinit var page: SetupPage
-
- init {
- itemView.tag = this
- }
-
- fun bind(page: SetupPage) {
- this.page = page
-
- if (page.stepCompleted.invoke() == StepState.COMPLETE) {
+ AbstractViewHolder<SetupPage>(binding), SetupCallback {
+ override fun bind(model: SetupPage) {
+ if (model.stepCompleted.invoke() == StepState.COMPLETE) {
binding.buttonAction.visibility = View.INVISIBLE
binding.textConfirmation.visibility = View.VISIBLE
}
@@ -50,31 +37,31 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
activity.resources,
- page.iconId,
+ model.iconId,
activity.theme
)
)
- binding.textTitle.text = activity.resources.getString(page.titleId)
+ binding.textTitle.text = activity.resources.getString(model.titleId)
binding.textDescription.text =
- Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
+ Html.fromHtml(activity.resources.getString(model.descriptionId), 0)
binding.buttonAction.apply {
- text = activity.resources.getString(page.buttonTextId)
- if (page.buttonIconId != 0) {
+ text = activity.resources.getString(model.buttonTextId)
+ if (model.buttonIconId != 0) {
icon = ResourcesCompat.getDrawable(
activity.resources,
- page.buttonIconId,
+ model.buttonIconId,
activity.theme
)
}
iconGravity =
- if (page.leftAlignedIcon) {
+ if (model.leftAlignedIcon) {
MaterialButton.ICON_GRAVITY_START
} else {
MaterialButton.ICON_GRAVITY_END
}
setOnClickListener {
- page.buttonAction.invoke(this@SetupPageViewHolder)
+ model.buttonAction.invoke(this@SetupPageViewHolder)
}
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index a1620fbb7..5b5f800c1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -76,8 +76,8 @@ class AboutFragment : Fragment() {
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
}
- binding.textBuildHash.text = BuildConfig.GIT_HASH
- binding.buttonBuildHash.setOnClickListener {
+ binding.textVersionName.text = BuildConfig.VERSION_NAME
+ binding.textVersionName.setOnClickListener {
val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)
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 cc71254dc..9dabb9c41 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
@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.fragments
+import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -13,20 +14,26 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.DriverAdapter
import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.NativeConfig
import java.io.File
import java.io.IOException
@@ -55,12 +62,43 @@ class DriverManagerFragment : Fragment() {
return binding.root
}
+ // This is using the correct scope, lint is just acting up
+ @SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
driverViewModel.onOpenDriverManager(args.game)
+ if (NativeConfig.isPerGameConfigLoaded()) {
+ binding.toolbarDrivers.inflateMenu(R.menu.menu_driver_manager)
+ driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+ binding.toolbarDrivers.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.menu_driver_clear -> {
+ StringSetting.DRIVER_PATH.global = true
+ driverViewModel.updateDriverList()
+ (binding.listDrivers.adapter as DriverAdapter)
+ .replaceList(driverViewModel.driverList.value)
+ driverViewModel.showClearButton(false)
+ true
+ }
+
+ else -> false
+ }
+ }
+
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ driverViewModel.showClearButton.collect {
+ binding.toolbarDrivers.menu
+ .findItem(R.id.menu_driver_clear).isVisible = it
+ }
+ }
+ }
+ }
+ }
if (!driverViewModel.isInteractionAllowed.value) {
DriversLoadingDialogFragment().show(
@@ -85,25 +123,6 @@ class DriverManagerFragment : Fragment() {
adapter = DriverAdapter(driverViewModel)
}
- viewLifecycleOwner.lifecycleScope.apply {
- launch {
- driverViewModel.driverList.collectLatest {
- (binding.listDrivers.adapter as DriverAdapter).submitList(it)
- }
- }
- launch {
- driverViewModel.newDriverInstalled.collect {
- if (_binding != null && it) {
- (binding.listDrivers.adapter as DriverAdapter).apply {
- notifyItemChanged(driverViewModel.previouslySelectedDriver)
- notifyItemChanged(driverViewModel.selectedDriver)
- driverViewModel.setNewDriverInstalled(false)
- }
- }
- }
- }
- }
-
setInsets()
}
@@ -160,7 +179,7 @@ class DriverManagerFragment : Fragment() {
false
) {
val driverPath =
- "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+ "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
val driverFile = File(driverPath)
// Ignore file exceptions when a user selects an invalid zip
@@ -177,12 +196,21 @@ class DriverManagerFragment : Fragment() {
val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
val driverInList =
- driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+ driverViewModel.driverData.firstOrNull { it.second == driverData }
if (driverInList != null) {
return@newInstance getString(R.string.driver_already_installed)
} else {
- driverViewModel.addDriver(Pair(driverPath, driverData))
- driverViewModel.setNewDriverInstalled(true)
+ driverViewModel.onDriverAdded(Pair(driverPath, driverData))
+ withContext(Dispatchers.Main) {
+ if (_binding != null) {
+ val adapter = binding.listDrivers.adapter as DriverAdapter
+ adapter.addItem(driverData.toDriver())
+ adapter.selectItem(adapter.currentList.indices.last)
+ driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+ binding.listDrivers
+ .smoothScrollToPosition(adapter.currentList.indices.last)
+ }
+ }
}
return@newInstance Any()
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
new file mode 100644
index 000000000..de342212a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+
+data class Driver(
+ override var selected: Boolean,
+ val title: String,
+ val version: String = "",
+ val description: String = ""
+) : SelectableItem {
+ override fun onSelectionStateChanged(selected: Boolean) {
+ this.selected = selected
+ }
+
+ companion object {
+ fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver =
+ Driver(
+ selected,
+ this.name ?: "",
+ this.version ?: "",
+ this.description ?: ""
+ )
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
index 76accf8f3..15ae3a42b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -17,11 +18,10 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
import org.yuzu.yuzu_emu.utils.NativeConfig
-import java.io.BufferedOutputStream
import java.io.File
class DriverViewModel : ViewModel() {
@@ -38,97 +38,81 @@ class DriverViewModel : ViewModel() {
!loading && ready && !deleting
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false)
- private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers())
- val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
+ var driverData = GpuDriverHelper.getDrivers()
- var previouslySelectedDriver = 0
- var selectedDriver = -1
+ private val _driverList = MutableStateFlow(emptyList<Driver>())
+ val driverList: StateFlow<List<Driver>> get() = _driverList
// Used for showing which driver is currently installed within the driver manager card
private val _selectedDriverTitle = MutableStateFlow("")
val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
- private val _newDriverInstalled = MutableStateFlow(false)
- val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
+ private val _showClearButton = MutableStateFlow(false)
+ val showClearButton = _showClearButton.asStateFlow()
- val driversToDelete = mutableListOf<String>()
+ private val driversToDelete = mutableListOf<String>()
init {
- val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData
- findSelectedDriver(currentDriverMetadata)
-
- // If a user had installed a driver before the manager was implemented, this zips
- // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
- // be indexed and exported as expected.
- if (selectedDriver == -1) {
- val driverToSave =
- File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
- driverToSave.createNewFile()
- FileUtil.zipFromInternalStorage(
- File(GpuDriverHelper.driverInstallationPath!!),
- GpuDriverHelper.driverInstallationPath!!,
- BufferedOutputStream(driverToSave.outputStream())
- )
- _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
- setSelectedDriverIndex(_driverList.value.size - 1)
- }
+ updateDriverList()
+ updateDriverNameForGame(null)
+ }
- // If a user had installed a driver before the config was reworked to be multiplatform,
- // we have save the path of the previously selected driver to the new setting.
- if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 &&
- StringSetting.DRIVER_PATH.global
- ) {
- StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first)
- NativeConfig.saveGlobalConfig()
- } else {
- findSelectedDriver(GpuDriverHelper.customDriverSettingData)
+ fun reloadDriverData() {
+ _areDriversLoading.value = true
+ driverData = GpuDriverHelper.getDrivers()
+ updateDriverList()
+ _areDriversLoading.value = false
+ }
+
+ fun updateDriverList() {
+ val selectedDriver = GpuDriverHelper.customDriverSettingData
+ val newDriverList = mutableListOf(
+ Driver(
+ selectedDriver == GpuDriverMetadata(),
+ YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ )
+ )
+ driverData.forEach {
+ newDriverList.add(it.second.toDriver(it.second == selectedDriver))
}
- updateDriverNameForGame(null)
+ _driverList.value = newDriverList
}
- fun setSelectedDriverIndex(value: Int) {
- if (selectedDriver != -1) {
- previouslySelectedDriver = selectedDriver
+ fun onOpenDriverManager(game: Game?) {
+ if (game != null) {
+ SettingsFile.loadCustomConfig(game)
}
- selectedDriver = value
+ updateDriverList()
}
- fun setNewDriverInstalled(value: Boolean) {
- _newDriverInstalled.value = value
+ fun showClearButton(value: Boolean) {
+ _showClearButton.value = value
}
- fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
- val driverIndex = _driverList.value.indexOfFirst { it == driverData }
- if (driverIndex == -1) {
- _driverList.value.add(driverData)
- setSelectedDriverIndex(_driverList.value.size - 1)
- _selectedDriverTitle.value = driverData.second.name
- ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ fun onDriverSelected(position: Int) {
+ if (position == 0) {
+ StringSetting.DRIVER_PATH.setString("")
} else {
- setSelectedDriverIndex(driverIndex)
+ StringSetting.DRIVER_PATH.setString(driverData[position - 1].first)
}
}
- fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
- _driverList.value.remove(driverData)
+ fun onDriverRemoved(removedPosition: Int, selectedPosition: Int) {
+ driversToDelete.add(driverData[removedPosition - 1].first)
+ driverData.removeAt(removedPosition - 1)
+ onDriverSelected(selectedPosition)
}
- fun onOpenDriverManager(game: Game?) {
- if (game != null) {
- SettingsFile.loadCustomConfig(game)
- }
-
- val driverPath = StringSetting.DRIVER_PATH.getString()
- if (driverPath.isEmpty()) {
- setSelectedDriverIndex(0)
- } else {
- findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath)))
+ fun onDriverAdded(driver: Pair<String, GpuDriverMetadata>) {
+ if (driversToDelete.contains(driver.first)) {
+ driversToDelete.remove(driver.first)
}
+ driverData.add(driver)
+ onDriverSelected(driverData.size)
}
fun onCloseDriverManager(game: Game?) {
_isDeletingDrivers.value = true
- StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first)
updateDriverNameForGame(game)
if (game == null) {
NativeConfig.saveGlobalConfig()
@@ -181,20 +165,6 @@ class DriverViewModel : ViewModel() {
}
}
- private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) {
- if (driverList.value.size == 1) {
- setSelectedDriverIndex(0)
- return
- }
-
- driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> ->
- if (driver.second == currentDriverMetadata) {
- setSelectedDriverIndex(i)
- return
- }
- }
- }
-
fun updateDriverNameForGame(game: Game?) {
if (!GpuDriverHelper.supportsCustomDriverLoading()) {
return
@@ -217,7 +187,6 @@ class DriverViewModel : ViewModel() {
private fun setDriverReady() {
_isDriverReady.value = true
- _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
- ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ updateName()
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
new file mode 100644
index 000000000..11c22d323
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+interface SelectableItem {
+ var selected: Boolean
+ fun onSelectionStateChanged(selected: Boolean)
+}
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 622ae996e..644289e25 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
@@ -41,6 +41,7 @@ import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
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.TaskState
@@ -58,6 +59,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val gamesViewModel: GamesViewModel by viewModels()
private val taskViewModel: TaskViewModel by viewModels()
private val addonViewModel: AddonViewModel by viewModels()
+ private val driverViewModel: DriverViewModel by viewModels()
override var themeId: Int = 0
@@ -689,6 +691,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
NativeLibrary.initializeSystem(true)
NativeConfig.initializeGlobalConfig()
gamesViewModel.reloadGames(false)
+ driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
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 685272288..a8f9dcc34 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
@@ -62,9 +62,6 @@ object GpuDriverHelper {
?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
?.distinct()
?.toMutableList() ?: mutableListOf()
-
- // TODO: Get system driver information
- drivers.add(0, Pair("", GpuDriverMetadata()))
return drivers
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
new file mode 100644
index 000000000..7101ad434
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.viewholder
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import org.yuzu.yuzu_emu.adapters.AbstractDiffAdapter
+import org.yuzu.yuzu_emu.adapters.AbstractListAdapter
+
+/**
+ * [RecyclerView.ViewHolder] meant to work together with a [AbstractDiffAdapter] or a
+ * [AbstractListAdapter] so we can run [bind] on each list item without needing a manual hookup.
+ */
+abstract class AbstractViewHolder<Model>(binding: ViewBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ abstract fun bind(model: Model)
+}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 136c8dee6..ed3b1353a 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -410,8 +410,8 @@ void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
- handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
handheld->Disconnect();
}
}
@@ -770,8 +770,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
ASSERT(user_id);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
- EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
- FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
+ {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, 1,
+ user_id->AsU128(), 0);
const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
if (!Common::FS::CreateParentDirs(full_path)) {
@@ -878,7 +878,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
FileSys::Mode::Read);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
- system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
+ {}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
program_id, user_id->AsU128(), 0);
return ToJString(env, user_save_data_path);
}
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
index a26ffbc73..655e49219 100644
--- a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
@@ -147,7 +147,7 @@
android:layout_marginHorizontal="20dp" />
<LinearLayout
- android:id="@+id/button_build_hash"
+ android:id="@+id/button_version_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
@@ -164,7 +164,7 @@
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_build_hash"
+ android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index a24f5230e..38090fa50 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -148,7 +148,7 @@
android:layout_marginHorizontal="20dp" />
<LinearLayout
- android:id="@+id/button_build_hash"
+ android:id="@+id/button_version_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp"
@@ -165,7 +165,7 @@
android:text="@string/build" />
<com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_build_hash"
+ android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/src/android/app/src/main/res/menu/menu_driver_manager.xml b/src/android/app/src/main/res/menu/menu_driver_manager.xml
new file mode 100644
index 000000000..dee5d57b6
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_driver_manager.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_driver_clear"
+ android:icon="@drawable/ic_clear"
+ android:title="@string/clear"
+ app:showAsAction="always" />
+
+</menu>
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 166dc3dce..85dc18c11 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/page_table.h"
+#include "common/scope_exit.h"
namespace Common {
@@ -11,29 +12,10 @@ PageTable::~PageTable() noexcept = default;
bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
Common::ProcessAddress address) const {
- // Setup invalid defaults.
- out_entry->phys_addr = 0;
- out_entry->block_size = page_size;
- out_context->next_page = 0;
-
- // Validate that we can read the actual entry.
- const auto page = address / page_size;
- if (page >= backing_addr.size()) {
- return false;
- }
-
- // Validate that the entry is mapped.
- const auto phys_addr = backing_addr[page];
- if (phys_addr == 0) {
- return false;
- }
+ out_context->next_offset = GetInteger(address);
+ out_context->next_page = address / page_size;
- // Populate the results.
- out_entry->phys_addr = phys_addr + GetInteger(address);
- out_context->next_page = page + 1;
- out_context->next_offset = GetInteger(address) + page_size;
-
- return true;
+ return this->ContinueTraversal(out_entry, out_context);
}
bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
@@ -41,6 +23,12 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
out_entry->phys_addr = 0;
out_entry->block_size = page_size;
+ // Regardless of whether the page was mapped, advance on exit.
+ SCOPE_EXIT({
+ context->next_page += 1;
+ context->next_offset += page_size;
+ });
+
// Validate that we can read the actual entry.
const auto page = context->next_page;
if (page >= backing_addr.size()) {
@@ -55,8 +43,6 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
// Populate the results.
out_entry->phys_addr = phys_addr + context->next_offset;
- context->next_page = page + 1;
- context->next_offset += page_size;
return true;
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 753f55ebe..293d9647b 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -490,6 +490,10 @@ add_library(core STATIC
hle/service/filesystem/fsp_pr.h
hle/service/filesystem/fsp_srv.cpp
hle/service/filesystem/fsp_srv.h
+ hle/service/filesystem/romfs_controller.cpp
+ hle/service/filesystem/romfs_controller.h
+ hle/service/filesystem/save_data_controller.cpp
+ hle/service/filesystem/save_data_controller.h
hle/service/fgm/fgm.cpp
hle/service/fgm/fgm.h
hle/service/friend/friend.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c063f7719..461eea9c8 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -413,6 +413,7 @@ struct System::Impl {
kernel.ShutdownCores();
services.reset();
service_manager.reset();
+ fs_controller.Reset();
cheat_engine.reset();
telemetry_session.reset();
time_manager.Shutdown();
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 0e270eb50..e86aae846 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -114,7 +114,7 @@ public:
}
Kernel::KThread* GetActiveThread() override {
- return state->active_thread;
+ return state->active_thread.GetPointerUnsafe();
}
private:
@@ -147,11 +147,14 @@ private:
std::scoped_lock lk{connection_lock};
+ // Find the process we are going to debug.
+ SetDebugProcess();
+
// Ensure everything is stopped.
PauseEmulation();
// Set up the new frontend.
- frontend = std::make_unique<GDBStub>(*this, system);
+ frontend = std::make_unique<GDBStub>(*this, system, debug_process.GetPointerUnsafe());
// Set the new state. This will tear down any existing state.
state = ConnectionState{
@@ -194,15 +197,20 @@ private:
UpdateActiveThread();
if (state->info.type == SignalType::Watchpoint) {
- frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
+ frontend->Watchpoint(std::addressof(*state->active_thread),
+ *state->info.watchpoint);
} else {
- frontend->Stopped(state->active_thread);
+ frontend->Stopped(std::addressof(*state->active_thread));
}
break;
case SignalType::ShuttingDown:
frontend->ShuttingDown();
+ // Release members.
+ state->active_thread.Reset(nullptr);
+ debug_process.Reset(nullptr);
+
// Wait for emulation to shut down gracefully now.
state->signal_pipe.close();
state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
@@ -222,7 +230,7 @@ private:
stopped = true;
PauseEmulation();
UpdateActiveThread();
- frontend->Stopped(state->active_thread);
+ frontend->Stopped(state->active_thread.GetPointerUnsafe());
break;
}
case DebuggerAction::Continue:
@@ -232,7 +240,7 @@ private:
MarkResumed([&] {
state->active_thread->SetStepState(Kernel::StepState::StepPending);
state->active_thread->Resume(Kernel::SuspendType::Debug);
- ResumeEmulation(state->active_thread);
+ ResumeEmulation(state->active_thread.GetPointerUnsafe());
});
break;
case DebuggerAction::StepThreadLocked: {
@@ -255,6 +263,7 @@ private:
}
void PauseEmulation() {
+ Kernel::KScopedLightLock ll{debug_process->GetListLock()};
Kernel::KScopedSchedulerLock sl{system.Kernel()};
// Put all threads to sleep on next scheduler round.
@@ -264,6 +273,9 @@ private:
}
void ResumeEmulation(Kernel::KThread* except = nullptr) {
+ Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+ Kernel::KScopedSchedulerLock sl{system.Kernel()};
+
// Wake up all threads.
for (auto& thread : ThreadList()) {
if (std::addressof(thread) == except) {
@@ -277,15 +289,16 @@ private:
template <typename Callback>
void MarkResumed(Callback&& cb) {
- Kernel::KScopedSchedulerLock sl{system.Kernel()};
stopped = false;
cb();
}
void UpdateActiveThread() {
+ Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+
auto& threads{ThreadList()};
for (auto& thread : threads) {
- if (std::addressof(thread) == state->active_thread) {
+ if (std::addressof(thread) == state->active_thread.GetPointerUnsafe()) {
// Thread is still alive, no need to update.
return;
}
@@ -293,12 +306,18 @@ private:
state->active_thread = std::addressof(threads.front());
}
+private:
+ void SetDebugProcess() {
+ debug_process = std::move(system.Kernel().GetProcessList().back());
+ }
+
Kernel::KProcess::ThreadList& ThreadList() {
- return system.ApplicationProcess()->GetThreadList();
+ return debug_process->GetThreadList();
}
private:
System& system;
+ Kernel::KScopedAutoObject<Kernel::KProcess> debug_process;
std::unique_ptr<DebuggerFrontend> frontend;
boost::asio::io_context io_context;
@@ -310,7 +329,7 @@ private:
boost::process::async_pipe signal_pipe;
SignalInfo info;
- Kernel::KThread* active_thread;
+ Kernel::KScopedAutoObject<Kernel::KThread> active_thread;
std::array<u8, 4096> client_data;
bool pipe_data;
};
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 4051ed4af..80091cc7e 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -108,9 +108,9 @@ static std::string EscapeXML(std::string_view data) {
return escaped;
}
-GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
- : DebuggerFrontend(backend_), system{system_} {
- if (system.ApplicationProcess()->Is64Bit()) {
+GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_, Kernel::KProcess* debug_process_)
+ : DebuggerFrontend(backend_), system{system_}, debug_process{debug_process_} {
+ if (GetProcess()->Is64Bit()) {
arch = std::make_unique<GDBStubA64>();
} else {
arch = std::make_unique<GDBStubA32>();
@@ -276,7 +276,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
std::vector<u8> mem(size);
- if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
+ if (GetMemory().ReadBlock(addr, mem.data(), size)) {
// Restore any bytes belonging to replaced instructions.
auto it = replaced_instructions.lower_bound(addr);
for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
@@ -310,8 +310,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const auto mem_substr{std::string_view(command).substr(mem_sep)};
const auto mem{Common::HexStringToVector(mem_substr, false)};
- if (system.ApplicationMemory().WriteBlock(addr, mem.data(), size)) {
- Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, size);
+ if (GetMemory().WriteBlock(addr, mem.data(), size)) {
+ Core::InvalidateInstructionCacheRange(GetProcess(), addr, size);
SendReply(GDB_STUB_REPLY_OK);
} else {
SendReply(GDB_STUB_REPLY_ERR);
@@ -353,7 +353,7 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
- if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
+ if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
SendReply(GDB_STUB_REPLY_ERR);
return;
}
@@ -362,22 +362,20 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
switch (type) {
case BreakpointType::Software:
- replaced_instructions[addr] = system.ApplicationMemory().Read32(addr);
- system.ApplicationMemory().Write32(addr, arch->BreakpointInstruction());
- Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
+ replaced_instructions[addr] = GetMemory().Read32(addr);
+ GetMemory().Write32(addr, arch->BreakpointInstruction());
+ Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
success = true;
break;
case BreakpointType::WriteWatch:
- success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
- Kernel::DebugWatchpointType::Write);
+ success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
break;
case BreakpointType::ReadWatch:
- success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
- Kernel::DebugWatchpointType::Read);
+ success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
break;
case BreakpointType::AccessWatch:
- success = system.ApplicationProcess()->InsertWatchpoint(
- addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+ success =
+ GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
break;
case BreakpointType::Hardware:
default:
@@ -400,7 +398,7 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
- if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
+ if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
SendReply(GDB_STUB_REPLY_ERR);
return;
}
@@ -411,24 +409,22 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
case BreakpointType::Software: {
const auto orig_insn{replaced_instructions.find(addr)};
if (orig_insn != replaced_instructions.end()) {
- system.ApplicationMemory().Write32(addr, orig_insn->second);
- Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
+ GetMemory().Write32(addr, orig_insn->second);
+ Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
replaced_instructions.erase(addr);
success = true;
}
break;
}
case BreakpointType::WriteWatch:
- success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
- Kernel::DebugWatchpointType::Write);
+ success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
break;
case BreakpointType::ReadWatch:
- success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
- Kernel::DebugWatchpointType::Read);
+ success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
break;
case BreakpointType::AccessWatch:
- success = system.ApplicationProcess()->RemoveWatchpoint(
- addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+ success =
+ GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
break;
case BreakpointType::Hardware:
default:
@@ -466,10 +462,10 @@ void GDBStub::HandleQuery(std::string_view command) {
const auto target_xml{arch->GetTargetXML()};
SendReply(PaginateBuffer(target_xml, command.substr(30)));
} else if (command.starts_with("Offsets")) {
- const auto main_offset = Core::FindMainModuleEntrypoint(system.ApplicationProcess());
+ const auto main_offset = Core::FindMainModuleEntrypoint(GetProcess());
SendReply(fmt::format("TextSeg={:x}", GetInteger(main_offset)));
} else if (command.starts_with("Xfer:libraries:read::")) {
- auto modules = Core::FindModules(system.ApplicationProcess());
+ auto modules = Core::FindModules(GetProcess());
std::string buffer;
buffer += R"(<?xml version="1.0"?>)";
@@ -483,7 +479,7 @@ void GDBStub::HandleQuery(std::string_view command) {
SendReply(PaginateBuffer(buffer, command.substr(21)));
} else if (command.starts_with("fThreadInfo")) {
// beginning of list
- const auto& threads = system.ApplicationProcess()->GetThreadList();
+ const auto& threads = GetProcess()->GetThreadList();
std::vector<std::string> thread_ids;
for (const auto& thread : threads) {
thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
@@ -497,7 +493,7 @@ void GDBStub::HandleQuery(std::string_view command) {
buffer += R"(<?xml version="1.0"?>)";
buffer += "<threads>";
- const auto& threads = system.ApplicationProcess()->GetThreadList();
+ const auto& threads = GetProcess()->GetThreadList();
for (const auto& thread : threads) {
auto thread_name{Core::GetThreadName(&thread)};
if (!thread_name) {
@@ -613,7 +609,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
std::string reply;
- auto* process = system.ApplicationProcess();
+ auto* process = GetProcess();
auto& page_table = process->GetPageTable();
const char* commands = "Commands:\n"
@@ -714,7 +710,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
}
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
- auto& threads{system.ApplicationProcess()->GetThreadList()};
+ auto& threads{GetProcess()->GetThreadList()};
for (auto& thread : threads) {
if (thread.GetThreadId() == thread_id) {
return std::addressof(thread);
@@ -783,4 +779,12 @@ void GDBStub::SendStatus(char status) {
backend.WriteToClient(buf);
}
+Kernel::KProcess* GDBStub::GetProcess() {
+ return debug_process;
+}
+
+Core::Memory::Memory& GDBStub::GetMemory() {
+ return GetProcess()->GetMemory();
+}
+
} // namespace Core
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
index 368197920..232dcf49f 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -12,13 +12,22 @@
#include "core/debugger/debugger_interface.h"
#include "core/debugger/gdbstub_arch.h"
+namespace Kernel {
+class KProcess;
+}
+
+namespace Core::Memory {
+class Memory;
+}
+
namespace Core {
class System;
class GDBStub : public DebuggerFrontend {
public:
- explicit GDBStub(DebuggerBackend& backend, Core::System& system);
+ explicit GDBStub(DebuggerBackend& backend, Core::System& system,
+ Kernel::KProcess* debug_process);
~GDBStub() override;
void Connected() override;
@@ -42,8 +51,12 @@ private:
void SendReply(std::string_view data);
void SendStatus(char status);
+ Kernel::KProcess* GetProcess();
+ Core::Memory::Memory& GetMemory();
+
private:
Core::System& system;
+ Kernel::KProcess* debug_process;
std::unique_ptr<GDBStubArch> arch;
std::vector<char> current_command;
std::map<VAddr, u32> replaced_instructions;
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 12b3bd797..23196cd5f 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -97,8 +97,9 @@ std::string SaveDataAttribute::DebugInfo() const {
static_cast<u8>(rank), index);
}
-SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_)
- : dir{std::move(save_directory_)}, system{system_} {
+SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
+ VirtualDir save_directory_)
+ : system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
@@ -110,7 +111,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
- GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+ GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
return dir->CreateDirectoryRelative(save_directory);
}
@@ -118,7 +119,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory =
- GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+ GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
auto out = dir->GetDirectoryRelative(save_directory);
@@ -147,14 +148,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
}
}
-std::string SaveDataFactory::GetFullPath(Core::System& system, VirtualDir dir,
+std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
if (title_id == 0) {
- title_id = system.GetApplicationProcessProgramID();
+ title_id = program_id;
}
}
@@ -201,7 +202,7 @@ std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future)
SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
u128 user_id) const {
const auto path =
- GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
@@ -220,7 +221,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
SaveDataSize new_value) const {
const auto path =
- GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index fd4887e99..30d96928e 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -87,10 +87,13 @@ constexpr const char* GetSaveDataSizeFileName() {
return ".yuzu_save_size";
}
+using ProgramId = u64;
+
/// File system interface to the SaveData archive
class SaveDataFactory {
public:
- explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
+ explicit SaveDataFactory(Core::System& system_, ProgramId program_id_,
+ VirtualDir save_directory_);
~SaveDataFactory();
VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
@@ -99,7 +102,7 @@ public:
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
- static std::string GetFullPath(Core::System& system, VirtualDir dir, SaveDataSpaceId space,
+ static std::string GetFullPath(ProgramId program_id, VirtualDir dir, SaveDataSpaceId space,
SaveDataType type, u64 title_id, u128 user_id, u64 save_id);
static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
@@ -110,8 +113,9 @@ public:
void SetAutoCreate(bool state);
private:
- VirtualDir dir;
Core::System& system;
+ ProgramId program_id;
+ VirtualDir dir;
bool auto_create{true};
};
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index 34fe23b6a..e04d884ba 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -47,7 +47,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
// Connect controllers based on the following priority list from highest to lowest priority:
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
if (parameters.allow_pro_controller) {
- controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
controller->Connect(true);
} else if (parameters.allow_dual_joycons) {
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp
index 58a1e7216..f08a6e448 100644
--- a/src/core/hle/kernel/k_memory_block_manager.cpp
+++ b/src/core/hle/kernel/k_memory_block_manager.cpp
@@ -28,14 +28,14 @@ Result KMemoryBlockManager::Initialize(KProcessAddress st, KProcessAddress nd,
}
void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager,
- HostUnmapCallback&& host_unmap_callback) {
+ BlockCallback&& block_callback) {
// Erase every block until we have none left.
auto it = m_memory_block_tree.begin();
while (it != m_memory_block_tree.end()) {
KMemoryBlock* block = std::addressof(*it);
it = m_memory_block_tree.erase(it);
+ block_callback(block->GetAddress(), block->GetSize());
slab_manager->Free(block);
- host_unmap_callback(block->GetAddress(), block->GetSize());
}
ASSERT(m_memory_block_tree.empty());
diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h
index cb7b6f430..377628504 100644
--- a/src/core/hle/kernel/k_memory_block_manager.h
+++ b/src/core/hle/kernel/k_memory_block_manager.h
@@ -85,11 +85,11 @@ public:
public:
KMemoryBlockManager();
- using HostUnmapCallback = std::function<void(Common::ProcessAddress, u64)>;
+ using BlockCallback = std::function<void(Common::ProcessAddress, u64)>;
Result Initialize(KProcessAddress st, KProcessAddress nd,
KMemoryBlockSlabManager* slab_manager);
- void Finalize(KMemoryBlockSlabManager* slab_manager, HostUnmapCallback&& host_unmap_callback);
+ void Finalize(KMemoryBlockSlabManager* slab_manager, BlockCallback&& block_callback);
iterator end() {
return m_memory_block_tree.end();
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
index 73fbda331..3f0a39d33 100644
--- a/src/core/hle/kernel/k_page_table_base.cpp
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -431,15 +431,43 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
m_memory_block_slab_manager));
}
+Result KPageTableBase::FinalizeProcess() {
+ // Only process tables should be finalized.
+ ASSERT(!this->IsKernel());
+
+ // NOTE: Here Nintendo calls an unknown OnFinalize function.
+ // this->OnFinalize();
+
+ // NOTE: Here Nintendo calls a second unknown OnFinalize function.
+ // this->OnFinalize2();
+
+ // NOTE: Here Nintendo does a page table walk to discover heap pages to free.
+ // We will use the block manager finalization below to free them.
+
+ R_SUCCEED();
+}
+
void KPageTableBase::Finalize() {
- auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
- if (Settings::IsFastmemEnabled()) {
+ this->FinalizeProcess();
+
+ auto BlockCallback = [&](KProcessAddress addr, u64 size) {
+ if (m_impl->fastmem_arena) {
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
}
+
+ // Get physical pages.
+ KPageGroup pg(m_kernel, m_block_info_manager);
+ this->MakePageGroup(pg, addr, size / PageSize);
+
+ // Free the pages.
+ pg.CloseAndReset();
};
// Finalize memory blocks.
- m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
+ {
+ KScopedLightLock lk(m_general_lock);
+ m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(BlockCallback));
+ }
// Free any unsafe mapped memory.
if (m_mapped_unsafe_physical_memory) {
diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h
index 077cafc96..748419f86 100644
--- a/src/core/hle/kernel/k_page_table_base.h
+++ b/src/core/hle/kernel/k_page_table_base.h
@@ -241,6 +241,7 @@ public:
KResourceLimit* resource_limit, Core::Memory::Memory& memory,
KProcessAddress aslr_space_start);
+ Result FinalizeProcess();
void Finalize();
bool IsKernel() const {
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 068e71dff..ae332a550 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -171,6 +171,12 @@ void KProcess::Finalize() {
m_resource_limit->Close();
}
+ // Clear expensive resources, as the destructor is not called for guest objects.
+ for (auto& interface : m_arm_interfaces) {
+ interface.reset();
+ }
+ m_exclusive_monitor.reset();
+
// Perform inherited finalization.
KSynchronizationObject::Finalize();
}
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 1030f0c12..f3683cdcc 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -112,7 +112,14 @@ struct KernelCore::Impl {
old_process->Close();
}
- process_list.clear();
+ {
+ std::scoped_lock lk{process_list_lock};
+ for (auto* const process : process_list) {
+ process->Terminate();
+ process->Close();
+ }
+ process_list.clear();
+ }
next_object_id = 0;
next_kernel_process_id = KProcess::InitialProcessIdMin;
@@ -770,6 +777,7 @@ struct KernelCore::Impl {
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
+ std::mutex process_list_lock;
std::vector<KProcess*> process_list;
std::atomic<KProcess*> application_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
@@ -869,9 +877,19 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() {
}
void KernelCore::AppendNewProcess(KProcess* process) {
+ process->Open();
+
+ std::scoped_lock lk{impl->process_list_lock};
impl->process_list.push_back(process);
}
+void KernelCore::RemoveProcess(KProcess* process) {
+ std::scoped_lock lk{impl->process_list_lock};
+ if (std::erase(impl->process_list, process)) {
+ process->Close();
+ }
+}
+
void KernelCore::MakeApplicationProcess(KProcess* process) {
impl->MakeApplicationProcess(process);
}
@@ -884,8 +902,15 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process;
}
-const std::vector<KProcess*>& KernelCore::GetProcessList() const {
- return impl->process_list;
+std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
+ std::list<KScopedAutoObject<KProcess>> processes;
+ std::scoped_lock lk{impl->process_list_lock};
+
+ for (auto* const process : impl->process_list) {
+ processes.emplace_back(process);
+ }
+
+ return processes;
}
Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 5d4102145..8ea5bed1c 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -5,6 +5,7 @@
#include <array>
#include <functional>
+#include <list>
#include <memory>
#include <string>
#include <unordered_map>
@@ -116,8 +117,9 @@ public:
/// Retrieves a shared pointer to the system resource limit instance.
KResourceLimit* GetSystemResourceLimit();
- /// Adds the given shared pointer to an internal list of active processes.
+ /// Adds/removes the given pointer to an internal list of active processes.
void AppendNewProcess(KProcess* process);
+ void RemoveProcess(KProcess* process);
/// Makes the given process the new application process.
void MakeApplicationProcess(KProcess* process);
@@ -129,7 +131,7 @@ public:
const KProcess* ApplicationProcess() const;
/// Retrieves the list of processes.
- const std::vector<KProcess*>& GetProcessList() const;
+ std::list<KScopedAutoObject<KProcess>> GetProcessList();
/// Gets the sole instance of the global scheduler
Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp
index caa8bee9a..5c3e8829f 100644
--- a/src/core/hle/kernel/svc/svc_process.cpp
+++ b/src/core/hle/kernel/svc/svc_process.cpp
@@ -74,13 +74,15 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc
}
auto& memory = GetCurrentMemory(kernel);
- const auto& process_list = kernel.GetProcessList();
+ auto process_list = kernel.GetProcessList();
+ auto it = process_list.begin();
+
const auto num_processes = process_list.size();
const auto copy_amount =
std::min(static_cast<std::size_t>(out_process_ids_size), num_processes);
- for (std::size_t i = 0; i < copy_amount; ++i) {
- memory.Write64(out_process_ids, process_list[i]->GetProcessId());
+ for (std::size_t i = 0; i < copy_amount && it != process_list.end(); ++i, ++it) {
+ memory.Write64(out_process_ids, (*it)->GetProcessId());
out_process_ids += sizeof(u64);
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9e05bdafa..a768bdc54 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -36,6 +36,7 @@
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
@@ -2178,7 +2179,7 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) {
attribute.type = FileSys::SaveDataType::SaveData;
FileSys::VirtualDir save_data{};
- const auto res = system.GetFileSystemController().CreateSaveData(
+ const auto res = system.GetFileSystemController().OpenSaveDataController()->CreateSaveData(
&save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
@@ -2353,7 +2354,7 @@ void IApplicationFunctions::ExtendSaveData(HLERequestContext& ctx) {
"new_journal={:016X}",
static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
- system.GetFileSystemController().WriteSaveDataSize(
+ system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
type, system.GetApplicationProcessProgramID(), user_id,
{new_normal_size, new_journal_size});
@@ -2378,7 +2379,7 @@ void IApplicationFunctions::GetSaveDataSize(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1],
user_id[0]);
- const auto size = system.GetFileSystemController().ReadSaveDataSize(
+ const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
type, system.GetApplicationProcessProgramID(), user_id);
IPC::ResponseBuilder rb{ctx, 6};
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 780f8c74d..ca6d8d607 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -24,15 +24,13 @@
#include "core/hle/service/filesystem/fsp_ldr.h"
#include "core/hle/service/filesystem/fsp_pr.h"
#include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/hle/service/filesystem/romfs_controller.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
#include "core/hle/service/server_manager.h"
#include "core/loader/loader.h"
namespace Service::FileSystem {
-// A default size for normal/journal save data size if application control metadata cannot be found.
-// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
-constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
-
static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
std::string_view dir_name_) {
std::string dir_name(Common::FS::SanitizePath(dir_name_));
@@ -297,145 +295,65 @@ FileSystemController::FileSystemController(Core::System& system_) : system{syste
FileSystemController::~FileSystemController() = default;
-Result FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
- romfs_factory = std::move(factory);
- LOG_DEBUG(Service_FS, "Registered RomFS");
- return ResultSuccess;
-}
-
-Result FileSystemController::RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
- ASSERT_MSG(save_data_factory == nullptr, "Tried to register a second save data");
- save_data_factory = std::move(factory);
- LOG_DEBUG(Service_FS, "Registered save data");
- return ResultSuccess;
-}
+Result FileSystemController::RegisterProcess(
+ ProcessId process_id, ProgramId program_id,
+ std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
+ std::scoped_lock lk{registration_lock};
-Result FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
- ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC");
- sdmc_factory = std::move(factory);
- LOG_DEBUG(Service_FS, "Registered SDMC");
- return ResultSuccess;
-}
+ registrations.emplace(process_id, Registration{
+ .program_id = program_id,
+ .romfs_factory = std::move(romfs_factory),
+ .save_data_factory = CreateSaveDataFactory(program_id),
+ });
-Result FileSystemController::RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
- ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
- bis_factory = std::move(factory);
- LOG_DEBUG(Service_FS, "Registered BIS");
+ LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
return ResultSuccess;
}
-void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
- LOG_TRACE(Service_FS, "Setting packed update for romfs");
-
- if (romfs_factory == nullptr)
- return;
-
- romfs_factory->SetPackedUpdate(std::move(update_raw));
-}
-
-FileSys::VirtualFile FileSystemController::OpenRomFSCurrentProcess() const {
- LOG_TRACE(Service_FS, "Opening RomFS for current process");
-
- if (romfs_factory == nullptr) {
- return nullptr;
- }
-
- return romfs_factory->OpenCurrentProcess(system.GetApplicationProcessProgramID());
-}
-
-FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id,
- FileSys::ContentRecordType type) const {
- LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
-
- if (romfs_factory == nullptr) {
- return nullptr;
- }
-
- return romfs_factory->OpenPatchedRomFS(title_id, type);
-}
-
-FileSys::VirtualFile FileSystemController::OpenPatchedRomFSWithProgramIndex(
- u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
- LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
- program_index);
-
- if (romfs_factory == nullptr) {
- return nullptr;
- }
-
- return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
-}
-
-FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
- FileSys::ContentRecordType type) const {
- LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
- title_id, storage_id, type);
-
- if (romfs_factory == nullptr) {
- return nullptr;
- }
-
- return romfs_factory->Open(title_id, storage_id, type);
-}
-
-std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca(
- u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
- return romfs_factory->GetEntry(title_id, storage_id, type);
-}
-
-Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data,
- FileSys::SaveDataSpaceId space,
- const FileSys::SaveDataAttribute& save_struct) const {
- LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
- save_struct.DebugInfo());
+Result FileSystemController::OpenProcess(
+ ProgramId* out_program_id, std::shared_ptr<SaveDataController>* out_save_data_controller,
+ std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id) {
+ std::scoped_lock lk{registration_lock};
- if (save_data_factory == nullptr) {
+ const auto it = registrations.find(process_id);
+ if (it == registrations.end()) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- auto save_data = save_data_factory->Create(space, save_struct);
- if (save_data == nullptr) {
- return FileSys::ERROR_ENTITY_NOT_FOUND;
- }
-
- *out_save_data = save_data;
+ *out_program_id = it->second.program_id;
+ *out_save_data_controller =
+ std::make_shared<SaveDataController>(system, it->second.save_data_factory);
+ *out_romfs_controller =
+ std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
return ResultSuccess;
}
-Result FileSystemController::OpenSaveData(FileSys::VirtualDir* out_save_data,
- FileSys::SaveDataSpaceId space,
- const FileSys::SaveDataAttribute& attribute) const {
- LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space,
- attribute.DebugInfo());
-
- if (save_data_factory == nullptr) {
- return FileSys::ERROR_ENTITY_NOT_FOUND;
- }
+void FileSystemController::SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw) {
+ LOG_TRACE(Service_FS, "Setting packed update for romfs");
- auto save_data = save_data_factory->Open(space, attribute);
- if (save_data == nullptr) {
- return FileSys::ERROR_ENTITY_NOT_FOUND;
+ std::scoped_lock lk{registration_lock};
+ const auto it = registrations.find(process_id);
+ if (it == registrations.end()) {
+ return;
}
- *out_save_data = save_data;
- return ResultSuccess;
+ it->second.romfs_factory->SetPackedUpdate(std::move(update_raw));
}
-Result FileSystemController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
- FileSys::SaveDataSpaceId space) const {
- LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space);
-
- if (save_data_factory == nullptr) {
- return FileSys::ERROR_ENTITY_NOT_FOUND;
- }
+std::shared_ptr<SaveDataController> FileSystemController::OpenSaveDataController() {
+ return std::make_shared<SaveDataController>(system, CreateSaveDataFactory(ProgramId{}));
+}
- auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space);
- if (save_data_space == nullptr) {
- return FileSys::ERROR_ENTITY_NOT_FOUND;
- }
+std::shared_ptr<FileSys::SaveDataFactory> FileSystemController::CreateSaveDataFactory(
+ ProgramId program_id) {
+ using YuzuPath = Common::FS::YuzuPath;
+ const auto rw_mode = FileSys::Mode::ReadWrite;
- *out_save_data_space = save_data_space;
- return ResultSuccess;
+ auto vfs = system.GetFilesystem();
+ const auto nand_directory =
+ vfs->OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
+ return std::make_shared<FileSys::SaveDataFactory>(system, program_id,
+ std::move(nand_directory));
}
Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
@@ -540,48 +458,6 @@ u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const {
return 0;
}
-FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataType type,
- u64 title_id, u128 user_id) const {
- if (save_data_factory == nullptr) {
- return {0, 0};
- }
-
- const auto value = save_data_factory->ReadSaveDataSize(type, title_id, user_id);
-
- if (value.normal == 0 && value.journal == 0) {
- FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE};
-
- FileSys::NACP nacp;
- const auto res = system.GetAppLoader().ReadControlData(nacp);
-
- if (res != Loader::ResultStatus::Success) {
- const FileSys::PatchManager pm{system.GetApplicationProcessProgramID(),
- system.GetFileSystemController(),
- system.GetContentProvider()};
- const auto metadata = pm.GetControlMetadata();
- const auto& nacp_unique = metadata.first;
-
- if (nacp_unique != nullptr) {
- new_size = {nacp_unique->GetDefaultNormalSaveSize(),
- nacp_unique->GetDefaultJournalSaveSize()};
- }
- } else {
- new_size = {nacp.GetDefaultNormalSaveSize(), nacp.GetDefaultJournalSaveSize()};
- }
-
- WriteSaveDataSize(type, title_id, user_id, new_size);
- return new_size;
- }
-
- return value;
-}
-
-void FileSystemController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
- FileSys::SaveDataSize new_value) const {
- if (save_data_factory != nullptr)
- save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
-}
-
void FileSystemController::SetGameCard(FileSys::VirtualFile file) {
gamecard = std::make_unique<FileSys::XCI>(file);
const auto dir = gamecard->ConcatenatedPseudoDirectory();
@@ -801,14 +677,9 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
return bis_factory->GetBCATDirectory(title_id);
}
-void FileSystemController::SetAutoSaveDataCreation(bool enable) {
- save_data_factory->SetAutoCreate(enable);
-}
-
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (overwrite) {
bis_factory = nullptr;
- save_data_factory = nullptr;
sdmc_factory = nullptr;
}
@@ -836,11 +707,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
bis_factory->GetUserNANDContents());
}
- if (save_data_factory == nullptr) {
- save_data_factory =
- std::make_unique<FileSys::SaveDataFactory>(system, std::move(nand_directory));
- }
-
if (sdmc_factory == nullptr) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
std::move(sd_load_directory));
@@ -849,12 +715,19 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
}
+void FileSystemController::Reset() {
+ std::scoped_lock lk{registration_lock};
+ registrations.clear();
+}
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ const auto FileSystemProxyFactory = [&] { return std::make_shared<FSP_SRV>(system); };
+
server_manager->RegisterNamedService("fsp-ldr", std::make_shared<FSP_LDR>(system));
server_manager->RegisterNamedService("fsp:pr", std::make_shared<FSP_PR>(system));
- server_manager->RegisterNamedService("fsp-srv", std::make_shared<FSP_SRV>(system));
+ server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 276d264e1..48f37d289 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -43,6 +43,9 @@ class ServiceManager;
namespace FileSystem {
+class RomFsController;
+class SaveDataController;
+
enum class ContentStorageId : u32 {
System,
User,
@@ -61,32 +64,24 @@ enum class OpenDirectoryMode : u64 {
};
DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
+using ProcessId = u64;
+using ProgramId = u64;
+
class FileSystemController {
public:
explicit FileSystemController(Core::System& system_);
~FileSystemController();
- Result RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
- Result RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
- Result RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
- Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
-
- void SetPackedUpdate(FileSys::VirtualFile update_raw);
- FileSys::VirtualFile OpenRomFSCurrentProcess() const;
- FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type) const;
- FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
- FileSys::ContentRecordType type) const;
- FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
- FileSys::ContentRecordType type) const;
- std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
- FileSys::ContentRecordType type) const;
-
- Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
- const FileSys::SaveDataAttribute& save_struct) const;
- Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
- const FileSys::SaveDataAttribute& save_struct) const;
- Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
- FileSys::SaveDataSpaceId space) const;
+ Result RegisterProcess(ProcessId process_id, ProgramId program_id,
+ std::shared_ptr<FileSys::RomFSFactory>&& factory);
+ Result OpenProcess(ProgramId* out_program_id,
+ std::shared_ptr<SaveDataController>* out_save_data_controller,
+ std::shared_ptr<RomFsController>* out_romfs_controller,
+ ProcessId process_id);
+ void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
+
+ std::shared_ptr<SaveDataController> OpenSaveDataController();
+
Result OpenSDMC(FileSys::VirtualDir* out_sdmc) const;
Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
FileSys::BisPartitionId id) const;
@@ -96,11 +91,6 @@ public:
u64 GetFreeSpaceSize(FileSys::StorageId id) const;
u64 GetTotalSpaceSize(FileSys::StorageId id) const;
- FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
- u128 user_id) const;
- void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
- FileSys::SaveDataSize new_value) const;
-
void SetGameCard(FileSys::VirtualFile file);
FileSys::XCI* GetGameCard() const;
@@ -133,15 +123,24 @@ public:
FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
- void SetAutoSaveDataCreation(bool enable);
-
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
+ void Reset();
+
private:
- std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
- std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
+ std::shared_ptr<FileSys::SaveDataFactory> CreateSaveDataFactory(ProgramId program_id);
+
+ struct Registration {
+ ProgramId program_id;
+ std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
+ std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
+ };
+
+ std::mutex registration_lock;
+ std::map<ProcessId, Registration> registrations;
+
std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
std::unique_ptr<FileSys::BISFactory> bis_factory;
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 82ecc1b90..a2397bec4 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -27,6 +27,8 @@
#include "core/hle/result.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/hle/service/filesystem/romfs_controller.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/reporter.h"
@@ -577,9 +579,11 @@ private:
class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
public:
- explicit ISaveDataInfoReader(Core::System& system_, FileSys::SaveDataSpaceId space,
- FileSystemController& fsc_)
- : ServiceFramework{system_, "ISaveDataInfoReader"}, fsc{fsc_} {
+ explicit ISaveDataInfoReader(Core::System& system_,
+ std::shared_ptr<SaveDataController> save_data_controller_,
+ FileSys::SaveDataSpaceId space)
+ : ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
+ save_data_controller_} {
static const FunctionInfo functions[] = {
{0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
};
@@ -626,7 +630,7 @@ private:
void FindAllSaves(FileSys::SaveDataSpaceId space) {
FileSys::VirtualDir save_root{};
- const auto result = fsc.OpenSaveDataSpace(&save_root, space);
+ const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
if (result != ResultSuccess || save_root == nullptr) {
LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
@@ -723,7 +727,8 @@ private:
};
static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
- FileSystemController& fsc;
+ ProcessId process_id = 0;
+ std::shared_ptr<SaveDataController> save_data_controller;
std::vector<SaveDataInfo> info;
u64 next_entry_index = 0;
};
@@ -863,21 +868,20 @@ FSP_SRV::FSP_SRV(Core::System& system_)
if (Settings::values.enable_fs_access_log) {
access_log_mode = AccessLogMode::SdCard;
}
-
- // This should be true on creation
- fsc.SetAutoSaveDataCreation(true);
}
FSP_SRV::~FSP_SRV() = default;
void FSP_SRV::SetCurrentProcess(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- current_process_id = rp.Pop<u64>();
+ current_process_id = ctx.GetPID();
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
+ const auto res =
+ fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id);
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(res);
}
void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
@@ -916,7 +920,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
uid[1], uid[0]);
FileSys::VirtualDir save_data_dir{};
- fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct);
+ save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser,
+ save_struct);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -931,7 +936,8 @@ void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx)
LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
FileSys::VirtualDir save_data_dir{};
- fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
+ save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem,
+ save_struct);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -950,7 +956,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
LOG_INFO(Service_FS, "called.");
FileSys::VirtualDir dir{};
- auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute);
+ auto result =
+ save_data_controller->OpenSaveData(&dir, parameters.space_id, parameters.attribute);
if (result != ResultSuccess) {
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
@@ -1001,7 +1008,7 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<ISaveDataInfoReader>(
- std::make_shared<ISaveDataInfoReader>(system, space, fsc));
+ std::make_shared<ISaveDataInfoReader>(system, save_data_controller, space));
}
void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
@@ -1009,8 +1016,8 @@ void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISaveDataInfoReader>(system, FileSys::SaveDataSpaceId::TemporaryStorage,
- fsc);
+ rb.PushIpcInterface<ISaveDataInfoReader>(system, save_data_controller,
+ FileSys::SaveDataSpaceId::TemporaryStorage);
}
void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) {
@@ -1050,7 +1057,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
if (!romfs) {
- auto current_romfs = fsc.OpenRomFSCurrentProcess();
+ auto current_romfs = romfs_controller->OpenRomFSCurrentProcess();
if (!current_romfs) {
// TODO (bunnei): Find the right error code to use here
LOG_CRITICAL(Service_FS, "no file system interface available!");
@@ -1078,7 +1085,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
storage_id, unknown, title_id);
- auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
+ auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (!data) {
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
@@ -1101,7 +1108,8 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
const FileSys::PatchManager pm{title_id, fsc, content_provider};
- auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
+ auto base =
+ romfs_controller->OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
auto storage = std::make_shared<IStorage>(
system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
@@ -1129,9 +1137,8 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
- auto patched_romfs =
- fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index,
- FileSys::ContentRecordType::Program);
+ auto patched_romfs = romfs_controller->OpenPatchedRomFSWithProgramIndex(
+ program_id, program_index, FileSys::ContentRecordType::Program);
if (!patched_romfs) {
// TODO: Find the right error code to use here
@@ -1152,7 +1159,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
void FSP_SRV::DisableAutoSaveDataCreation(HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
- fsc.SetAutoSaveDataCreation(false);
+ save_data_controller->SetAutoCreate(false);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 280bc9867..26980af99 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -17,6 +17,9 @@ class FileSystemBackend;
namespace Service::FileSystem {
+class RomFsController;
+class SaveDataController;
+
enum class AccessLogVersion : u32 {
V7_0_0 = 2,
@@ -67,6 +70,9 @@ private:
u64 current_process_id = 0;
u32 access_log_program_index = 0;
AccessLogMode access_log_mode = AccessLogMode::None;
+ u64 program_id = 0;
+ std::shared_ptr<SaveDataController> save_data_controller;
+ std::shared_ptr<RomFsController> romfs_controller;
};
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/romfs_controller.cpp b/src/core/hle/service/filesystem/romfs_controller.cpp
new file mode 100644
index 000000000..19c9cec72
--- /dev/null
+++ b/src/core/hle/service/filesystem/romfs_controller.cpp
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/filesystem/romfs_controller.h"
+
+namespace Service::FileSystem {
+
+RomFsController::RomFsController(std::shared_ptr<FileSys::RomFSFactory> factory_, u64 program_id_)
+ : factory{std::move(factory_)}, program_id{program_id_} {}
+RomFsController::~RomFsController() = default;
+
+FileSys::VirtualFile RomFsController::OpenRomFSCurrentProcess() {
+ return factory->OpenCurrentProcess(program_id);
+}
+
+FileSys::VirtualFile RomFsController::OpenPatchedRomFS(u64 title_id,
+ FileSys::ContentRecordType type) {
+ return factory->OpenPatchedRomFS(title_id, type);
+}
+
+FileSys::VirtualFile RomFsController::OpenPatchedRomFSWithProgramIndex(
+ u64 title_id, u8 program_index, FileSys::ContentRecordType type) {
+ return factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
+}
+
+FileSys::VirtualFile RomFsController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) {
+ return factory->Open(title_id, storage_id, type);
+}
+
+std::shared_ptr<FileSys::NCA> RomFsController::OpenBaseNca(u64 title_id,
+ FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type) {
+ return factory->GetEntry(title_id, storage_id, type);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/romfs_controller.h b/src/core/hle/service/filesystem/romfs_controller.h
new file mode 100644
index 000000000..9a478f71d
--- /dev/null
+++ b/src/core/hle/service/filesystem/romfs_controller.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Service::FileSystem {
+
+class RomFsController {
+public:
+ explicit RomFsController(std::shared_ptr<FileSys::RomFSFactory> factory_, u64 program_id_);
+ ~RomFsController();
+
+ FileSys::VirtualFile OpenRomFSCurrentProcess();
+ FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type);
+ FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+ FileSys::ContentRecordType type);
+ FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type);
+ std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
+ FileSys::ContentRecordType type);
+
+private:
+ const std::shared_ptr<FileSys::RomFSFactory> factory;
+ const u64 program_id;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/save_data_controller.cpp b/src/core/hle/service/filesystem/save_data_controller.cpp
new file mode 100644
index 000000000..d19b3ea1e
--- /dev/null
+++ b/src/core/hle/service/filesystem/save_data_controller.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
+#include "core/loader/loader.h"
+
+namespace Service::FileSystem {
+
+namespace {
+
+// A default size for normal/journal save data size if application control metadata cannot be found.
+// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
+constexpr u64 SufficientSaveDataSize = 0xF0000000;
+
+FileSys::SaveDataSize GetDefaultSaveDataSize(Core::System& system, u64 program_id) {
+ const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto metadata = pm.GetControlMetadata();
+ const auto& nacp = metadata.first;
+
+ if (nacp != nullptr) {
+ return {nacp->GetDefaultNormalSaveSize(), nacp->GetDefaultJournalSaveSize()};
+ }
+
+ return {SufficientSaveDataSize, SufficientSaveDataSize};
+}
+
+} // namespace
+
+SaveDataController::SaveDataController(Core::System& system_,
+ std::shared_ptr<FileSys::SaveDataFactory> factory_)
+ : system{system_}, factory{std::move(factory_)} {}
+SaveDataController::~SaveDataController() = default;
+
+Result SaveDataController::CreateSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute) {
+ LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
+ attribute.DebugInfo());
+
+ auto save_data = factory->Create(space, attribute);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
+}
+
+Result SaveDataController::OpenSaveData(FileSys::VirtualDir* out_save_data,
+ FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute) {
+ auto save_data = factory->Open(space, attribute);
+ if (save_data == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data = save_data;
+ return ResultSuccess;
+}
+
+Result SaveDataController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space) {
+ auto save_data_space = factory->GetSaveDataSpaceDirectory(space);
+ if (save_data_space == nullptr) {
+ return FileSys::ERROR_ENTITY_NOT_FOUND;
+ }
+
+ *out_save_data_space = save_data_space;
+ return ResultSuccess;
+}
+
+FileSys::SaveDataSize SaveDataController::ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
+ u128 user_id) {
+ const auto value = factory->ReadSaveDataSize(type, title_id, user_id);
+
+ if (value.normal == 0 && value.journal == 0) {
+ const auto size = GetDefaultSaveDataSize(system, title_id);
+ factory->WriteSaveDataSize(type, title_id, user_id, size);
+ return size;
+ }
+
+ return value;
+}
+
+void SaveDataController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
+ FileSys::SaveDataSize new_value) {
+ factory->WriteSaveDataSize(type, title_id, user_id, new_value);
+}
+
+void SaveDataController::SetAutoCreate(bool state) {
+ factory->SetAutoCreate(state);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/save_data_controller.h b/src/core/hle/service/filesystem/save_data_controller.h
new file mode 100644
index 000000000..863188e4c
--- /dev/null
+++ b/src/core/hle/service/filesystem/save_data_controller.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Service::FileSystem {
+
+class SaveDataController {
+public:
+ explicit SaveDataController(Core::System& system,
+ std::shared_ptr<FileSys::SaveDataFactory> factory_);
+ ~SaveDataController();
+
+ Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute);
+ Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+ const FileSys::SaveDataAttribute& attribute);
+ Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+ FileSys::SaveDataSpaceId space);
+
+ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id);
+ void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
+ FileSys::SaveDataSize new_value);
+ void SetAutoCreate(bool state);
+
+private:
+ Core::System& system;
+ const std::shared_ptr<FileSys::SaveDataFactory> factory;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index 6f1151b03..1254b6d49 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -15,9 +15,10 @@
namespace Service::Glue {
namespace {
-std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
- const auto& list = system.Kernel().GetProcessList();
- const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
+std::optional<u64> GetTitleIDForProcessID(Core::System& system, u64 process_id) {
+ auto list = system.Kernel().GetProcessList();
+
+ const auto iter = std::find_if(list.begin(), list.end(), [&process_id](auto& process) {
return process->GetProcessId() == process_id;
});
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fc03a0a5f..4ce0a9834 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -22,12 +22,10 @@ void LoopProcess(Core::System& system) {
std::shared_ptr<HidFirmwareSettings> firmware_settings =
std::make_shared<HidFirmwareSettings>();
- // TODO: Remove this hack until this service is emulated properly.
- const auto process_list = system.Kernel().GetProcessList();
- if (!process_list.empty()) {
- resource_manager->Initialize();
- resource_manager->RegisterAppletResourceUserId(process_list[0]->GetId(), true);
- }
+ // TODO: Remove this hack when am is emulated properly.
+ resource_manager->Initialize();
+ resource_manager->RegisterAppletResourceUserId(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 74898888a..1951da33b 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -1498,7 +1498,7 @@ void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
bool check_device_index = false;
switch (vibration_device_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index d92499f05..b52468e41 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -22,27 +22,26 @@ constexpr Result ResultProcessNotFound{ErrorModule::PM, 1};
constexpr u64 NO_PROCESS_FOUND_PID{0};
-std::optional<Kernel::KProcess*> SearchProcessList(
- const std::vector<Kernel::KProcess*>& process_list,
- std::function<bool(Kernel::KProcess*)> predicate) {
+using ProcessList = std::list<Kernel::KScopedAutoObject<Kernel::KProcess>>;
+
+template <typename F>
+Kernel::KScopedAutoObject<Kernel::KProcess> SearchProcessList(ProcessList& process_list,
+ F&& predicate) {
const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate);
if (iter == process_list.end()) {
- return std::nullopt;
+ return nullptr;
}
- return *iter;
+ return iter->GetPointerUnsafe();
}
-void GetApplicationPidGeneric(HLERequestContext& ctx,
- const std::vector<Kernel::KProcess*>& process_list) {
- const auto process = SearchProcessList(process_list, [](const auto& proc) {
- return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin;
- });
+void GetApplicationPidGeneric(HLERequestContext& ctx, ProcessList& process_list) {
+ auto process = SearchProcessList(process_list, [](auto& p) { return p->IsApplication(); });
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push(process.has_value() ? (*process)->GetProcessId() : NO_PROCESS_FOUND_PID);
+ rb.Push(process.IsNull() ? NO_PROCESS_FOUND_PID : process->GetProcessId());
}
} // Anonymous namespace
@@ -80,8 +79,7 @@ private:
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
- explicit DebugMonitor(Core::System& system_)
- : ServiceFramework{system_, "pm:dmnt"}, kernel{system_.Kernel()} {
+ explicit DebugMonitor(Core::System& system_) : ServiceFramework{system_, "pm:dmnt"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetJitDebugProcessIdList"},
@@ -106,12 +104,11 @@ private:
LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
- const auto process =
- SearchProcessList(kernel.GetProcessList(), [program_id](const auto& proc) {
- return proc->GetProgramId() == program_id;
- });
+ auto list = kernel.GetProcessList();
+ auto process = SearchProcessList(
+ list, [program_id](auto& p) { return p->GetProgramId() == program_id; });
- if (!process.has_value()) {
+ if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultProcessNotFound);
return;
@@ -119,12 +116,13 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push((*process)->GetProcessId());
+ rb.Push(process->GetProcessId());
}
void GetApplicationProcessId(HLERequestContext& ctx) {
LOG_DEBUG(Service_PM, "called");
- GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+ auto list = kernel.GetProcessList();
+ GetApplicationPidGeneric(ctx, list);
}
void AtmosphereGetProcessInfo(HLERequestContext& ctx) {
@@ -135,11 +133,10 @@ private:
LOG_WARNING(Service_PM, "(Partial Implementation) called, pid={:016X}", pid);
- const auto process = SearchProcessList(kernel.GetProcessList(), [pid](const auto& proc) {
- return proc->GetProcessId() == pid;
- });
+ auto list = kernel.GetProcessList();
+ auto process = SearchProcessList(list, [pid](auto& p) { return p->GetProcessId() == pid; });
- if (!process.has_value()) {
+ if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultProcessNotFound);
return;
@@ -159,7 +156,7 @@ private:
OverrideStatus override_status{};
ProgramLocation program_location{
- .program_id = (*process)->GetProgramId(),
+ .program_id = process->GetProgramId(),
.storage_id = 0,
};
@@ -169,14 +166,11 @@ private:
rb.PushRaw(program_location);
rb.PushRaw(override_status);
}
-
- const Kernel::KernelCore& kernel;
};
class Info final : public ServiceFramework<Info> {
public:
- explicit Info(Core::System& system_, const std::vector<Kernel::KProcess*>& process_list_)
- : ServiceFramework{system_, "pm:info"}, process_list{process_list_} {
+ explicit Info(Core::System& system_) : ServiceFramework{system_, "pm:info"} {
static const FunctionInfo functions[] = {
{0, &Info::GetProgramId, "GetProgramId"},
{65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
@@ -193,11 +187,11 @@ private:
LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id);
- const auto process = SearchProcessList(process_list, [process_id](const auto& proc) {
- return proc->GetProcessId() == process_id;
- });
+ auto list = kernel.GetProcessList();
+ auto process = SearchProcessList(
+ list, [process_id](auto& p) { return p->GetProcessId() == process_id; });
- if (!process.has_value()) {
+ if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultProcessNotFound);
return;
@@ -205,7 +199,7 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push((*process)->GetProgramId());
+ rb.Push(process->GetProgramId());
}
void AtmosphereGetProcessId(HLERequestContext& ctx) {
@@ -214,11 +208,11 @@ private:
LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
- const auto process = SearchProcessList(process_list, [program_id](const auto& proc) {
- return proc->GetProgramId() == program_id;
- });
+ auto list = system.Kernel().GetProcessList();
+ auto process = SearchProcessList(
+ list, [program_id](auto& p) { return p->GetProgramId() == program_id; });
- if (!process.has_value()) {
+ if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultProcessNotFound);
return;
@@ -226,16 +220,13 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push((*process)->GetProcessId());
+ rb.Push(process->GetProcessId());
}
-
- const std::vector<Kernel::KProcess*>& process_list;
};
class Shell final : public ServiceFramework<Shell> {
public:
- explicit Shell(Core::System& system_)
- : ServiceFramework{system_, "pm:shell"}, kernel{system_.Kernel()} {
+ explicit Shell(Core::System& system_) : ServiceFramework{system_, "pm:shell"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LaunchProgram"},
@@ -257,10 +248,9 @@ public:
private:
void GetApplicationProcessIdForShell(HLERequestContext& ctx) {
LOG_DEBUG(Service_PM, "called");
- GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+ auto list = kernel.GetProcessList();
+ GetApplicationPidGeneric(ctx, list);
}
-
- const Kernel::KernelCore& kernel;
};
void LoopProcess(Core::System& system) {
@@ -268,8 +258,7 @@ void LoopProcess(Core::System& system) {
server_manager->RegisterNamedService("pm:bm", std::make_shared<BootMode>(system));
server_manager->RegisterNamedService("pm:dmnt", std::make_shared<DebugMonitor>(system));
- server_manager->RegisterNamedService(
- "pm:info", std::make_shared<Info>(system, system.Kernel().GetProcessList()));
+ server_manager->RegisterNamedService("pm:info", std::make_shared<Info>(system));
server_manager->RegisterNamedService("pm:shell", std::make_shared<Shell>(system));
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 15edb23e0..8ef49387d 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -256,8 +256,13 @@ Result ServerManager::WaitAndProcessImpl() {
// Wait for a signal.
s32 out_index{-1};
- R_TRY(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index, wait_objs.data(),
- num_objs, -1));
+ R_TRY_CATCH(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index,
+ wait_objs.data(), num_objs, -1)) {
+ R_CATCH(Kernel::ResultSessionClosed) {
+ // On session closed, index is updated and we don't want to return an error.
+ }
+ }
+ R_END_TRY_CATCH;
ASSERT(out_index >= 0 && out_index < num_objs);
// Set the output index.
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index c9f8707b7..1e599e78b 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -216,20 +216,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
}
- // Find the RomFS by searching for a ".romfs" file in this directory
- const auto& files = dir->GetFiles();
- const auto romfs_iter =
- std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& f) {
- return f->GetName().find(".romfs") != std::string::npos;
- });
-
- // Register the RomFS if a ".romfs" file was found
- if (romfs_iter != files.end() && *romfs_iter != nullptr) {
- romfs = *romfs_iter;
- system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
- *this, system.GetContentProvider(), system.GetFileSystemController()));
- }
-
is_loaded = true;
return {ResultStatus::Success,
LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}};
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 814407535..2a32b1276 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -74,8 +74,10 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result;
}
- system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
- *this, system.GetContentProvider(), system.GetFileSystemController()));
+ system.GetFileSystemController().RegisterProcess(
+ process.GetProcessId(), nca->GetTitleId(),
+ std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
+ system.GetFileSystemController()));
is_loaded = true;
return load_result;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index e74697cda..f8225d697 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -275,10 +275,12 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
return {ResultStatus::ErrorLoadingNRO, {}};
}
- if (romfs != nullptr) {
- system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
- *this, system.GetContentProvider(), system.GetFileSystemController()));
- }
+ u64 program_id{};
+ ReadProgramId(program_id);
+ system.GetFileSystemController().RegisterProcess(
+ process.GetProcessId(), program_id,
+ std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
+ system.GetFileSystemController()));
is_loaded = true;
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index f4ab75b77..28116ff3a 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -111,7 +111,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(process.GetProcessId(),
+ std::move(update_raw));
}
is_loaded = true;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 12d72c380..e9abb199a 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -78,7 +78,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(process.GetProcessId(),
+ std::move(update_raw));
}
is_loaded = true;
diff --git a/src/hid_core/CMakeLists.txt b/src/hid_core/CMakeLists.txt
index cce4e6857..aa85502b5 100644
--- a/src/hid_core/CMakeLists.txt
+++ b/src/hid_core/CMakeLists.txt
@@ -36,6 +36,30 @@ add_library(hid_core STATIC
irsensor/processor_base.h
irsensor/tera_plugin_processor.cpp
irsensor/tera_plugin_processor.h
+ resources/abstracted_pad/abstract_battery_handler.cpp
+ resources/abstracted_pad/abstract_battery_handler.h
+ resources/abstracted_pad/abstract_button_handler.cpp
+ resources/abstracted_pad/abstract_button_handler.h
+ resources/abstracted_pad/abstract_ir_sensor_handler.cpp
+ resources/abstracted_pad/abstract_ir_sensor_handler.h
+ resources/abstracted_pad/abstract_led_handler.cpp
+ resources/abstracted_pad/abstract_led_handler.h
+ resources/abstracted_pad/abstract_mcu_handler.cpp
+ resources/abstracted_pad/abstract_mcu_handler.h
+ resources/abstracted_pad/abstract_nfc_handler.cpp
+ resources/abstracted_pad/abstract_nfc_handler.h
+ resources/abstracted_pad/abstract_pad.cpp
+ resources/abstracted_pad/abstract_pad.h
+ resources/abstracted_pad/abstract_pad_holder.cpp
+ resources/abstracted_pad/abstract_pad_holder.h
+ resources/abstracted_pad/abstract_palma_handler.cpp
+ resources/abstracted_pad/abstract_palma_handler.h
+ resources/abstracted_pad/abstract_properties_handler.cpp
+ resources/abstracted_pad/abstract_properties_handler.h
+ resources/abstracted_pad/abstract_sixaxis_handler.cpp
+ resources/abstracted_pad/abstract_sixaxis_handler.h
+ resources/abstracted_pad/abstract_vibration_handler.cpp
+ resources/abstracted_pad/abstract_vibration_handler.h
resources/debug_pad/debug_pad.cpp
resources/debug_pad/debug_pad.h
resources/debug_pad/debug_pad_types.h
@@ -56,6 +80,8 @@ add_library(hid_core STATIC
resources/npad/npad_resource.cpp
resources/npad/npad_resource.h
resources/npad/npad_types.h
+ resources/npad/npad_vibration.cpp
+ resources/npad/npad_vibration.h
resources/palma/palma.cpp
resources/palma/palma.h
resources/six_axis/console_six_axis.cpp
@@ -78,6 +104,14 @@ add_library(hid_core STATIC
resources/touch_screen/touch_types.h
resources/unique_pad/unique_pad.cpp
resources/unique_pad/unique_pad.h
+ resources/vibration/gc_vibration_device.h
+ resources/vibration/gc_vibration_device.cpp
+ resources/vibration/n64_vibration_device.h
+ resources/vibration/n64_vibration_device.cpp
+ resources/vibration/vibration_base.h
+ resources/vibration/vibration_base.cpp
+ resources/vibration/vibration_device.h
+ resources/vibration/vibration_device.cpp
resources/applet_resource.cpp
resources/applet_resource.h
resources/controller_base.cpp
diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp
index 3d2d1e9f9..a6a96935d 100644
--- a/src/hid_core/frontend/emulated_controller.cpp
+++ b/src/hid_core/frontend/emulated_controller.cpp
@@ -27,7 +27,7 @@ EmulatedController::~EmulatedController() = default;
NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
switch (type) {
case Settings::ControllerType::ProController:
- return NpadStyleIndex::ProController;
+ return NpadStyleIndex::Fullkey;
case Settings::ControllerType::DualJoyconDetached:
return NpadStyleIndex::JoyconDual;
case Settings::ControllerType::LeftJoycon:
@@ -49,13 +49,13 @@ NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerTyp
case Settings::ControllerType::SegaGenesis:
return NpadStyleIndex::SegaGenesis;
default:
- return NpadStyleIndex::ProController;
+ return NpadStyleIndex::Fullkey;
}
}
Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
switch (type) {
- case NpadStyleIndex::ProController:
+ case NpadStyleIndex::Fullkey:
return Settings::ControllerType::ProController;
case NpadStyleIndex::JoyconDual:
return Settings::ControllerType::DualJoyconDetached;
@@ -106,7 +106,7 @@ void EmulatedController::ReloadFromSettings() {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
original_npad_type = npad_type;
} else {
- SetNpadStyleIndex(NpadStyleIndex::ProController);
+ SetNpadStyleIndex(NpadStyleIndex::Fullkey);
original_npad_type = npad_type;
}
@@ -1073,7 +1073,7 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
.body = GetNpadColor(controller.color_values[index].body),
.button = GetNpadColor(controller.color_values[index].buttons),
};
- if (npad_type == NpadStyleIndex::ProController) {
+ if (npad_type == NpadStyleIndex::Fullkey) {
controller.colors_state.left = {
.body = GetNpadColor(controller.color_values[index].left_grip),
.button = GetNpadColor(controller.color_values[index].buttons),
@@ -1356,7 +1356,7 @@ bool EmulatedController::HasNfc() const {
switch (npad_type) {
case NpadStyleIndex::JoyconRight:
case NpadStyleIndex::JoyconDual:
- case NpadStyleIndex::ProController:
+ case NpadStyleIndex::Fullkey:
case NpadStyleIndex::Handheld:
break;
default:
@@ -1548,7 +1548,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Fullkey controllers to Pro controllers
if (IsControllerFullkey() && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
- SetNpadStyleIndex(NpadStyleIndex::ProController);
+ SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect();
return;
}
@@ -1556,13 +1556,13 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Dual joycon controllers to Pro controllers
if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
- SetNpadStyleIndex(NpadStyleIndex::ProController);
+ SetNpadStyleIndex(NpadStyleIndex::Fullkey);
Connect();
return;
}
// Fallback Pro controllers to Dual joycon
- if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) {
+ if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
Connect();
@@ -1577,7 +1577,7 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) {
- case NpadStyleIndex::ProController:
+ case NpadStyleIndex::Fullkey:
case NpadStyleIndex::GameCube:
case NpadStyleIndex::NES:
case NpadStyleIndex::SNES:
@@ -1593,7 +1593,7 @@ bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) {
- case NpadStyleIndex::ProController:
+ case NpadStyleIndex::Fullkey:
return supported_style_tag.fullkey.As<bool>();
case NpadStyleIndex::Handheld:
return supported_style_tag.handheld.As<bool>();
diff --git a/src/hid_core/hid_types.h b/src/hid_core/hid_types.h
index a81ed6af0..2c3f02f34 100644
--- a/src/hid_core/hid_types.h
+++ b/src/hid_core/hid_types.h
@@ -220,6 +220,7 @@ enum class NpadIdType : u32 {
};
enum class NpadInterfaceType : u8 {
+ None = 0,
Bluetooth = 1,
Rail = 2,
Usb = 3,
@@ -229,7 +230,7 @@ enum class NpadInterfaceType : u8 {
// This is nn::hid::NpadStyleIndex
enum class NpadStyleIndex : u8 {
None = 0,
- ProController = 3,
+ Fullkey = 3,
Handheld = 4,
HandheldNES = 4,
JoyconDual = 5,
diff --git a/src/hid_core/hid_util.h b/src/hid_core/hid_util.h
index 94ff2d23a..397a87472 100644
--- a/src/hid_core/hid_util.h
+++ b/src/hid_core/hid_util.h
@@ -42,7 +42,7 @@ constexpr Result IsSixaxisHandleValid(const Core::HID::SixAxisSensorHandle& hand
constexpr Result IsVibrationHandleValid(const Core::HID::VibrationDeviceHandle& handle) {
switch (handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
new file mode 100644
index 000000000..62fbbb0a7
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
@@ -0,0 +1,197 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/core_timing.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_battery_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractBatteryHandler::NpadAbstractBatteryHandler() {}
+
+NpadAbstractBatteryHandler::~NpadAbstractBatteryHandler() = default;
+
+void NpadAbstractBatteryHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractBatteryHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractBatteryHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractBatteryHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractBatteryHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+Result NpadAbstractBatteryHandler::UpdateBatteryState(u64 aruid) {
+ const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId());
+ AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+ if (aruid_data == nullptr) {
+ return ResultSuccess;
+ }
+
+ auto& npad_internal_state =
+ aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
+ auto& system_properties = npad_internal_state.system_properties;
+
+ system_properties.is_charging_joy_dual.Assign(dual_battery.is_charging);
+ system_properties.is_powered_joy_dual.Assign(dual_battery.is_powered);
+ system_properties.is_charging_joy_left.Assign(left_battery.is_charging);
+ system_properties.is_powered_joy_left.Assign(left_battery.is_powered);
+ system_properties.is_charging_joy_right.Assign(right_battery.is_charging);
+ system_properties.is_powered_joy_right.Assign(right_battery.is_powered);
+
+ npad_internal_state.battery_level_dual = dual_battery.battery_level;
+ npad_internal_state.battery_level_left = left_battery.battery_level;
+ npad_internal_state.battery_level_right = right_battery.battery_level;
+
+ return ResultSuccess;
+}
+
+void NpadAbstractBatteryHandler::UpdateBatteryState() {
+ if (ref_counter == 0) {
+ return;
+ }
+ has_new_battery_data = GetNewBatteryState();
+}
+
+bool NpadAbstractBatteryHandler::GetNewBatteryState() {
+ bool has_changed = false;
+ Core::HID::NpadPowerInfo new_dual_battery_state{};
+ Core::HID::NpadPowerInfo new_left_battery_state{};
+ Core::HID::NpadPowerInfo new_right_battery_state{};
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ const auto power_info = abstract_pad->power_info;
+ if (power_info.battery_level > Core::HID::NpadBatteryLevel::Full) {
+ // Abort
+ continue;
+ }
+
+ const auto style = abstract_pad->assignment_style;
+
+ if (style.is_external_assigned || style.is_handheld_assigned) {
+ new_dual_battery_state = power_info;
+ }
+ if (style.is_external_left_assigned || style.is_handheld_left_assigned) {
+ new_left_battery_state = power_info;
+ }
+ if (style.is_external_right_assigned || style.is_handheld_right_assigned) {
+ new_right_battery_state = power_info;
+ }
+
+ if (abstract_pad->internal_flags.is_battery_low_ovln_required) {
+ if (abstract_pad->interface_type == Core::HID::NpadInterfaceType::Rail) {
+ // TODO
+ }
+ abstract_pad->internal_flags.is_battery_low_ovln_required.Assign(false);
+ }
+ }
+
+ if (dual_battery.battery_level != new_dual_battery_state.battery_level ||
+ dual_battery.is_charging != new_dual_battery_state.is_charging ||
+ dual_battery.is_powered != new_dual_battery_state.is_powered) {
+ has_changed = true;
+ dual_battery = new_dual_battery_state;
+ }
+
+ if (left_battery.battery_level != new_left_battery_state.battery_level ||
+ left_battery.is_charging != new_left_battery_state.is_charging ||
+ left_battery.is_powered != new_left_battery_state.is_powered) {
+ has_changed = true;
+ left_battery = new_left_battery_state;
+ }
+
+ if (right_battery.battery_level != new_right_battery_state.battery_level ||
+ right_battery.is_charging != new_right_battery_state.is_charging ||
+ right_battery.is_powered != new_right_battery_state.is_powered) {
+ has_changed = true;
+ right_battery = new_right_battery_state;
+ }
+
+ return has_changed;
+}
+
+void NpadAbstractBatteryHandler::UpdateCoreBatteryState() {
+ if (ref_counter == 0) {
+ return;
+ }
+ if (!has_new_battery_data) {
+ return;
+ }
+
+ UpdateBatteryState(0);
+}
+
+void NpadAbstractBatteryHandler::InitializeBatteryState(u64 aruid) {
+ UpdateBatteryState(aruid);
+}
+
+bool NpadAbstractBatteryHandler::HasBattery() const {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ const auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ return abstract_pad->disabled_feature_set.has_fullkey_battery ||
+ abstract_pad->disabled_feature_set.has_left_right_joy_battery;
+ }
+
+ return false;
+}
+
+void NpadAbstractBatteryHandler::HasLeftRightBattery(bool& has_left, bool& has_right) const {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ has_left = false;
+ has_right = false;
+
+ for (std::size_t i = 0; i < count; i++) {
+ const auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (!abstract_pad->disabled_feature_set.has_fullkey_battery &&
+ !abstract_pad->disabled_feature_set.has_left_right_joy_battery) {
+ continue;
+ }
+ has_left = abstract_pad->assignment_style.is_external_left_assigned ||
+ abstract_pad->assignment_style.is_handheld_left_assigned;
+ has_right = abstract_pad->assignment_style.is_external_right_assigned ||
+ abstract_pad->assignment_style.is_handheld_right_assigned;
+ }
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
new file mode 100644
index 000000000..85ac5eb72
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractBatteryHandler final {
+public:
+ explicit NpadAbstractBatteryHandler();
+ ~NpadAbstractBatteryHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ Result UpdateBatteryState(u64 aruid);
+ void UpdateBatteryState();
+ bool GetNewBatteryState();
+ void UpdateCoreBatteryState();
+ void InitializeBatteryState(u64 aruid);
+
+ bool HasBattery() const;
+ void HasLeftRightBattery(bool& has_left, bool& has_right) const;
+
+private:
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+ Core::HID::NpadPowerInfo dual_battery{};
+ Core::HID::NpadPowerInfo left_battery{};
+ Core::HID::NpadPowerInfo right_battery{};
+ bool has_new_battery_data{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
new file mode 100644
index 000000000..587169433
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
@@ -0,0 +1,199 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_button_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractButtonHandler::NpadAbstractButtonHandler() {}
+
+NpadAbstractButtonHandler::~NpadAbstractButtonHandler() = default;
+
+void NpadAbstractButtonHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractButtonHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractButtonHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractButtonHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractButtonHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+Result NpadAbstractButtonHandler::UpdateAllButtonWithHomeProtection(u64 aruid) {
+ const Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+
+ if (data == nullptr) {
+ return ResultSuccess;
+ }
+
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateButtonLifo(npad_entry, aruid);
+
+ bool is_home_button_protection_enabled{};
+ const auto result = applet_resource_holder->shared_npad_resource->GetHomeProtectionEnabled(
+ is_home_button_protection_enabled, aruid, npad_id);
+
+ if (result.IsError()) {
+ return ResultSuccess;
+ }
+
+ npad_entry.internal_state.button_properties.is_home_button_protection_enabled.Assign(
+ is_home_button_protection_enabled);
+
+ return ResultSuccess;
+}
+
+void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
+ Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ for (std::size_t i = 0; i < AruidIndexMax; i++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateButtonLifo(npad_entry, data->aruid);
+ }
+}
+
+void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
+ Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ for (std::size_t i = 0; i < AruidIndexMax; i++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateButtonLifo(npad_entry, data->aruid);
+ }
+}
+
+void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
+ Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+ if (data == nullptr) {
+ return;
+ }
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateButtonLifo(npad_entry, aruid);
+}
+
+Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
+ const Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ auto result = applet_resource_holder->shared_npad_resource->SetHomeProtectionEnabled(
+ aruid, npad_id, is_enabled);
+ if (result.IsError()) {
+ return result;
+ }
+
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+ if (data == nullptr) {
+ return ResultSuccess;
+ }
+
+ bool is_home_protection_enabled{};
+ result = applet_resource_holder->shared_npad_resource->GetHomeProtectionEnabled(
+ is_home_protection_enabled, aruid, npad_id);
+ if (result.IsError()) {
+ return ResultSuccess;
+ }
+
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ npad_entry.internal_state.button_properties.is_home_button_protection_enabled.Assign(
+ is_home_protection_enabled);
+ return ResultSuccess;
+}
+
+bool NpadAbstractButtonHandler::IsButtonPressedOnConsoleMode() {
+ return is_button_pressed_on_console_mode;
+}
+
+void NpadAbstractButtonHandler::EnableCenterClamp() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ abstract_pad->internal_flags.use_center_clamp.Assign(true);
+ }
+}
+
+void NpadAbstractButtonHandler::UpdateButtonLifo(NpadSharedMemoryEntry& shared_memory, u64 aruid) {
+ auto* npad_resource = applet_resource_holder->shared_npad_resource;
+ Core::HID::NpadStyleTag style_tag = {properties_handler->GetStyleSet(aruid)};
+ style_tag.system_ext.Assign(npad_resource->GetActiveData()->GetNpadSystemExtState());
+
+ UpdateNpadFullkeyLifo(style_tag, 0, aruid, shared_memory);
+ UpdateHandheldLifo(style_tag, 1, aruid, shared_memory);
+ UpdateJoyconDualLifo(style_tag, 2, aruid, shared_memory);
+ UpdateJoyconLeftLifo(style_tag, 3, aruid, shared_memory);
+ UpdateJoyconRightLifo(style_tag, 4, aruid, shared_memory);
+ UpdatePalmaLifo(style_tag, 5, aruid, shared_memory);
+ UpdateSystemExtLifo(style_tag, 6, aruid, shared_memory);
+}
+
+void NpadAbstractButtonHandler::UpdateNpadFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateHandheldLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconDualLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconLeftLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconRightLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateSystemExtLifo(Core::HID::NpadStyleTag style_tag,
+ int style_index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+void NpadAbstractButtonHandler::UpdatePalmaLifo(Core::HID::NpadStyleTag style_tag, int style_index,
+ u64 aruid, NpadSharedMemoryEntry& shared_memory) {
+ // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.h b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h
new file mode 100644
index 000000000..01eafe96d
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct NpadSharedMemoryEntry;
+
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractButtonHandler final {
+public:
+ explicit NpadAbstractButtonHandler();
+ ~NpadAbstractButtonHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ Result UpdateAllButtonWithHomeProtection(u64 aruid);
+
+ void UpdateAllButtonLifo();
+ void UpdateCoreBatteryState();
+ void UpdateButtonState(u64 aruid);
+
+ Result SetHomeProtection(bool is_enabled, u64 aruid);
+ bool IsButtonPressedOnConsoleMode();
+ void EnableCenterClamp();
+
+ void UpdateButtonLifo(NpadSharedMemoryEntry& shared_memory, u64 aruid);
+
+ void UpdateNpadFullkeyLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdateHandheldLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdateJoyconDualLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdateJoyconLeftLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdateJoyconRightLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdateSystemExtLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+ void UpdatePalmaLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+ NpadSharedMemoryEntry& shared_memory);
+
+private:
+ struct GcTrigger {
+ float left;
+ float right;
+ };
+
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+
+ bool is_button_pressed_on_console_mode{};
+
+ u64 gc_sampling_number{};
+ GcTrigger gc_trigger_state{};
+};
+
+} // namespace Service::HID
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
new file mode 100644
index 000000000..d4e4181bf
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractIrSensorHandler::NpadAbstractIrSensorHandler() {}
+
+NpadAbstractIrSensorHandler::~NpadAbstractIrSensorHandler() = default;
+
+void NpadAbstractIrSensorHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractIrSensorHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractIrSensorHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractIrSensorHandler::UpdateIrSensorState() {
+ const auto previous_state = sensor_state;
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ if (count == 0) {
+ sensor_state = NpadIrSensorState::Disabled;
+ if (sensor_state == previous_state) {
+ return;
+ }
+ ir_sensor_event->Signal();
+ return;
+ }
+
+ bool is_found{};
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (!abstract_pad->disabled_feature_set.has_bluetooth_address) {
+ continue;
+ }
+ is_found = true;
+ xcd_handle = abstract_pad->xcd_handle;
+ }
+
+ if (is_found) {
+ if (sensor_state == NpadIrSensorState::Active) {
+ return;
+ }
+ sensor_state = NpadIrSensorState::Available;
+ if (sensor_state == previous_state) {
+ return;
+ }
+ ir_sensor_event->Signal();
+ return;
+ }
+
+ sensor_state = NpadIrSensorState::Unavailable;
+ if (sensor_state == previous_state) {
+ return;
+ }
+
+ ir_sensor_event->Signal();
+ return;
+}
+
+Result NpadAbstractIrSensorHandler::ActivateIrSensor(bool is_enabled) {
+ if (sensor_state == NpadIrSensorState::Unavailable) {
+ return ResultIrSensorIsNotReady;
+ }
+ if (is_enabled && sensor_state == NpadIrSensorState::Available) {
+ sensor_state = NpadIrSensorState::Active;
+ } else {
+ if (is_enabled) {
+ return ResultSuccess;
+ }
+ if (sensor_state != NpadIrSensorState::Active) {
+ return ResultSuccess;
+ }
+ sensor_state = NpadIrSensorState::Available;
+ }
+ ir_sensor_event->Signal();
+ return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::GetIrSensorEventHandle(Kernel::KReadableEvent** out_event) {
+ *out_event = &ir_sensor_event->GetReadableEvent();
+ return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::GetXcdHandleForNpadWithIrSensor(u64& handle) const {
+ if (sensor_state < NpadIrSensorState::Available) {
+ return ResultIrSensorIsNotReady;
+ }
+ handle = xcd_handle;
+ return ResultSuccess;
+}
+
+NpadIrSensorState NpadAbstractIrSensorHandler::GetSensorState() const {
+ return sensor_state;
+}
+
+} // namespace Service::HID
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
new file mode 100644
index 000000000..fe8e005af
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+enum class NpadIrSensorState : u32 {
+ Disabled,
+ Unavailable,
+ Available,
+ Active,
+};
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractIrSensorHandler final {
+public:
+ explicit NpadAbstractIrSensorHandler();
+ ~NpadAbstractIrSensorHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void UpdateIrSensorState();
+ Result ActivateIrSensor(bool param_2);
+
+ Result GetIrSensorEventHandle(Kernel::KReadableEvent** out_event);
+
+ Result GetXcdHandleForNpadWithIrSensor(u64& handle) const;
+
+ NpadIrSensorState GetSensorState() const;
+
+private:
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+ Kernel::KEvent* ir_sensor_event{nullptr};
+ u64 xcd_handle{};
+ NpadIrSensorState sensor_state{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp
new file mode 100644
index 000000000..0b2bfe88d
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/core_timing.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_led_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractLedHandler::NpadAbstractLedHandler() {}
+
+NpadAbstractLedHandler::~NpadAbstractLedHandler() = default;
+
+void NpadAbstractLedHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractLedHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractLedHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractLedHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractLedHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractLedHandler::SetNpadLedHandlerLedPattern() {
+ const auto npad_id = properties_handler->GetNpadId();
+
+ switch (npad_id) {
+ case Core::HID::NpadIdType::Player1:
+ left_pattern = Core::HID::LedPattern{1, 0, 0, 0};
+ break;
+ case Core::HID::NpadIdType::Player2:
+ left_pattern = Core::HID::LedPattern{1, 1, 0, 0};
+ break;
+ case Core::HID::NpadIdType::Player3:
+ left_pattern = Core::HID::LedPattern{1, 1, 1, 0};
+ break;
+ case Core::HID::NpadIdType::Player4:
+ left_pattern = Core::HID::LedPattern{1, 1, 1, 1};
+ break;
+ case Core::HID::NpadIdType::Player5:
+ left_pattern = Core::HID::LedPattern{1, 0, 0, 1};
+ break;
+ case Core::HID::NpadIdType::Player6:
+ left_pattern = Core::HID::LedPattern{1, 0, 1, 0};
+ break;
+ case Core::HID::NpadIdType::Player7:
+ left_pattern = Core::HID::LedPattern{1, 0, 1, 1};
+ break;
+ case Core::HID::NpadIdType::Player8:
+ left_pattern = Core::HID::LedPattern{0, 1, 1, 0};
+ break;
+ case Core::HID::NpadIdType::Other:
+ case Core::HID::NpadIdType::Handheld:
+ left_pattern = Core::HID::LedPattern{0, 0, 0, 0};
+ break;
+ default:
+ ASSERT_MSG(false, "Invalid npad id type");
+ break;
+ }
+
+ switch (npad_id) {
+ case Core::HID::NpadIdType::Player1:
+ right_pattern = Core::HID::LedPattern{0, 0, 0, 1};
+ break;
+ case Core::HID::NpadIdType::Player2:
+ right_pattern = Core::HID::LedPattern{0, 1, 1, 1};
+ break;
+ case Core::HID::NpadIdType::Player3:
+ right_pattern = Core::HID::LedPattern{0, 1, 1, 1};
+ break;
+ case Core::HID::NpadIdType::Player4:
+ right_pattern = Core::HID::LedPattern{1, 1, 1, 1};
+ break;
+ case Core::HID::NpadIdType::Player5:
+ right_pattern = Core::HID::LedPattern{1, 0, 0, 1};
+ break;
+ case Core::HID::NpadIdType::Player6:
+ right_pattern = Core::HID::LedPattern{0, 1, 0, 1};
+ break;
+ case Core::HID::NpadIdType::Player7:
+ right_pattern = Core::HID::LedPattern{1, 1, 0, 1};
+ break;
+ case Core::HID::NpadIdType::Player8:
+ right_pattern = Core::HID::LedPattern{0, 1, 1, 0};
+ break;
+ case Core::HID::NpadIdType::Other:
+ case Core::HID::NpadIdType::Handheld:
+ right_pattern = Core::HID::LedPattern{0, 0, 0, 0};
+ break;
+ default:
+ ASSERT_MSG(false, "Invalid npad id type");
+ break;
+ }
+}
+
+void NpadAbstractLedHandler::SetLedBlinkingDevice(Core::HID::LedPattern pattern) {
+ led_blinking = pattern;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.h b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h
new file mode 100644
index 000000000..09528129b
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractLedHandler final {
+public:
+ explicit NpadAbstractLedHandler();
+ ~NpadAbstractLedHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void SetNpadLedHandlerLedPattern();
+
+ void SetLedBlinkingDevice(Core::HID::LedPattern pattern);
+
+private:
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+ Core::HID::LedPattern led_blinking{0, 0, 0, 0};
+ Core::HID::LedPattern left_pattern{0, 0, 0, 0};
+ Core::HID::LedPattern right_pattern{0, 0, 0, 0};
+ u64 led_interval{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp
new file mode 100644
index 000000000..6f35bd95c
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_mcu_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractMcuHandler::NpadAbstractMcuHandler() {}
+
+NpadAbstractMcuHandler::~NpadAbstractMcuHandler() = default;
+
+void NpadAbstractMcuHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractMcuHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractMcuHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractMcuHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractMcuHandler::UpdateMcuState() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = properties_handler->GetAbstractedPads(abstract_pads);
+
+ if (count == 0) {
+ mcu_holder = {};
+ return;
+ }
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (!abstract_pad->disabled_feature_set.has_left_joy_rail_bus) {
+ if (!abstract_pad->disabled_feature_set.has_left_joy_six_axis_sensor &&
+ !abstract_pad->disabled_feature_set.has_right_joy_six_axis_sensor) {
+ continue;
+ }
+ if (mcu_holder[1].state != NpadMcuState::Active) {
+ mcu_holder[1].state = NpadMcuState::Available;
+ }
+ mcu_holder[1].abstracted_pad = abstract_pad;
+ continue;
+ }
+ if (mcu_holder[0].state != NpadMcuState::Active) {
+ mcu_holder[0].state = NpadMcuState::Available;
+ }
+ mcu_holder[0].abstracted_pad = abstract_pad;
+ }
+}
+
+Result NpadAbstractMcuHandler::GetAbstractedPad(IAbstractedPad** data, u32 mcu_index) {
+ if (mcu_holder[mcu_index].state == NpadMcuState::None ||
+ mcu_holder[mcu_index].abstracted_pad == nullptr) {
+ return ResultMcuIsNotReady;
+ }
+ *data = mcu_holder[mcu_index].abstracted_pad;
+ return ResultSuccess;
+}
+
+NpadMcuState NpadAbstractMcuHandler::GetMcuState(u32 mcu_index) {
+ return mcu_holder[mcu_index].state;
+}
+
+Result NpadAbstractMcuHandler::SetMcuState(bool is_enabled, u32 mcu_index) {
+ NpadMcuState& state = mcu_holder[mcu_index].state;
+
+ if (state == NpadMcuState::None) {
+ return ResultMcuIsNotReady;
+ }
+
+ if ((is_enabled) && (state == NpadMcuState::Available)) {
+ state = NpadMcuState::Active;
+ return ResultSuccess;
+ }
+
+ if (is_enabled) {
+ return ResultSuccess;
+ }
+ if (state != NpadMcuState::Active) {
+ return ResultSuccess;
+ }
+
+ state = NpadMcuState::Available;
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h
new file mode 100644
index 000000000..9902dd03a
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct IAbstractedPad;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+enum class NpadMcuState : u32 {
+ None,
+ Available,
+ Active,
+};
+
+struct NpadMcuHolder {
+ NpadMcuState state;
+ INSERT_PADDING_BYTES(0x4);
+ IAbstractedPad* abstracted_pad;
+};
+static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractMcuHandler final {
+public:
+ explicit NpadAbstractMcuHandler();
+ ~NpadAbstractMcuHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void UpdateMcuState();
+ Result GetAbstractedPad(IAbstractedPad** data, u32 mcu_index);
+ NpadMcuState GetMcuState(u32 mcu_index);
+ Result SetMcuState(bool is_enabled, u32 mcu_index);
+
+private:
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+ std::array<NpadMcuHolder, 2> mcu_holder{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp
new file mode 100644
index 000000000..bd9b79333
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_nfc_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractNfcHandler::NpadAbstractNfcHandler() {}
+
+NpadAbstractNfcHandler::~NpadAbstractNfcHandler() = default;
+
+void NpadAbstractNfcHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractNfcHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+Result NpadAbstractNfcHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractNfcHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractNfcHandler::UpdateNfcState() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = properties_handler->GetAbstractedPads(abstract_pads);
+
+ if (count == 0) {
+ if (sensor_state == NpadNfcState::Active) {
+ nfc_activate_event->Signal();
+ }
+ if (sensor_state == NpadNfcState::Unavailable) {
+ return;
+ }
+ sensor_state = NpadNfcState::Unavailable;
+ input_event->Signal();
+ return;
+ }
+
+ bool is_found{};
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (!abstract_pad->disabled_feature_set.has_nfc) {
+ continue;
+ }
+ is_found = true;
+ xcd_handle = 0;
+ }
+
+ if (is_found) {
+ if (sensor_state == NpadNfcState::Active) {
+ return;
+ }
+ if (sensor_state == NpadNfcState::Available) {
+ return;
+ }
+ sensor_state = NpadNfcState::Available;
+ input_event->Signal();
+ return;
+ }
+
+ if (sensor_state == NpadNfcState::Active) {
+ nfc_activate_event->Signal();
+ }
+ if (sensor_state == NpadNfcState::Unavailable) {
+ return;
+ }
+ sensor_state = NpadNfcState::Unavailable;
+ input_event->Signal();
+ return;
+}
+
+bool NpadAbstractNfcHandler::HasNfcSensor() {
+ return sensor_state != NpadNfcState::Unavailable;
+}
+
+bool NpadAbstractNfcHandler::IsNfcActivated() {
+ return sensor_state == NpadNfcState::Active;
+}
+
+Result NpadAbstractNfcHandler::GetAcquireNfcActivateEventHandle(
+ Kernel::KReadableEvent** out_event) {
+ *out_event = &nfc_activate_event->GetReadableEvent();
+ return ResultSuccess;
+}
+
+void NpadAbstractNfcHandler::SetInputEvent(Kernel::KEvent* event) {
+ input_event = event;
+}
+
+Result NpadAbstractNfcHandler::ActivateNfc(bool is_enabled) {
+ if (sensor_state == NpadNfcState::Active) {
+ return ResultNfcIsNotReady;
+ }
+
+ NpadNfcState new_state = NpadNfcState::Available;
+ if (is_enabled) {
+ new_state = NpadNfcState::Active;
+ }
+ if (sensor_state != new_state) {
+ sensor_state = new_state;
+ nfc_activate_event->Signal();
+ }
+ return ResultSuccess;
+}
+
+Result NpadAbstractNfcHandler::GetXcdHandleWithNfc(u64& out_xcd_handle) const {
+ if (sensor_state == NpadNfcState::Unavailable) {
+ return ResultNfcIsNotReady;
+ }
+ if (xcd_handle == 0) {
+ return ResultNfcXcdHandleIsNotInitialized;
+ }
+
+ out_xcd_handle = xcd_handle;
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h
new file mode 100644
index 000000000..0702722a6
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Kernel {
+class KReadableEvent;
+}
+
+enum class NpadNfcState : u32 {
+ Unavailable,
+ Available,
+ Active,
+};
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractNfcHandler final {
+public:
+ explicit NpadAbstractNfcHandler();
+ ~NpadAbstractNfcHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void UpdateNfcState();
+ bool HasNfcSensor();
+ bool IsNfcActivated();
+
+ Result GetAcquireNfcActivateEventHandle(Kernel::KReadableEvent** out_event);
+ void SetInputEvent(Kernel::KEvent* event);
+
+ Result ActivateNfc(bool is_enabled);
+
+ Result GetXcdHandleWithNfc(u64& out_xcd_handle) const;
+
+private:
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ s32 ref_counter{};
+ Kernel::KEvent* nfc_activate_event{nullptr};
+ Kernel::KEvent* input_event{nullptr};
+ u64 xcd_handle{};
+ NpadNfcState sensor_state{NpadNfcState::Unavailable};
+};
+} // 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
new file mode 100644
index 000000000..2c7691d7c
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp
@@ -0,0 +1,294 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+AbstractPad::AbstractPad() {}
+
+AbstractPad::~AbstractPad() = default;
+
+void AbstractPad::SetExternals(AppletResourceHolder* applet_resource,
+ CaptureButtonResource* capture_button_resource,
+ HomeButtonResource* home_button_resource,
+ SixAxisResource* sixaxis_resource, PalmaResource* palma_resource,
+ VibrationHandler* vibration) {
+ applet_resource_holder = applet_resource;
+
+ properties_handler.SetAppletResource(applet_resource_holder);
+ properties_handler.SetAbstractPadHolder(&abstract_pad_holder);
+
+ led_handler.SetAppletResource(applet_resource_holder);
+ led_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ led_handler.SetPropertiesHandler(&properties_handler);
+
+ ir_sensor_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ ir_sensor_handler.SetPropertiesHandler(&properties_handler);
+
+ nfc_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ nfc_handler.SetPropertiesHandler(&properties_handler);
+
+ 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.SetGcVibration(&vibration_gc);
+
+ sixaxis_handler.SetAppletResource(applet_resource_holder);
+ sixaxis_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ sixaxis_handler.SetPropertiesHandler(&properties_handler);
+ sixaxis_handler.SetSixaxisResource(sixaxis_resource);
+
+ button_handler.SetAppletResource(applet_resource_holder);
+ button_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ button_handler.SetPropertiesHandler(&properties_handler);
+
+ battery_handler.SetAppletResource(applet_resource_holder);
+ battery_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ battery_handler.SetPropertiesHandler(&properties_handler);
+
+ palma_handler.SetAbstractPadHolder(&abstract_pad_holder);
+ palma_handler.SetPropertiesHandler(&properties_handler);
+ palma_handler.SetPalmaResource(palma_resource);
+}
+
+void AbstractPad::SetNpadId(Core::HID::NpadIdType npad_id) {
+ properties_handler.SetNpadId(npad_id);
+}
+
+Result AbstractPad::Activate() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+
+ if (ref_counter != 0) {
+ ref_counter++;
+ return ResultSuccess;
+ }
+
+ std::size_t stage = 0;
+ Result result = ResultSuccess;
+
+ if (result.IsSuccess()) {
+ stage++;
+ result = properties_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = led_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = ir_sensor_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = mcu_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = nfc_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = vibration_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = sixaxis_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = button_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = battery_handler.IncrementRefCounter();
+ }
+ if (result.IsSuccess()) {
+ stage++;
+ result = palma_handler.IncrementRefCounter();
+ }
+
+ if (result.IsSuccess()) {
+ ref_counter++;
+ return result;
+ }
+
+ if (stage > 9) {
+ battery_handler.DecrementRefCounter();
+ }
+ if (stage > 8) {
+ button_handler.DecrementRefCounter();
+ }
+ if (stage > 7) {
+ sixaxis_handler.DecrementRefCounter();
+ }
+ if (stage > 6) {
+ vibration_handler.DecrementRefCounter();
+ }
+ if (stage > 5) {
+ nfc_handler.DecrementRefCounter();
+ }
+ if (stage > 4) {
+ mcu_handler.DecrementRefCounter();
+ }
+ if (stage > 3) {
+ ir_sensor_handler.DecrementRefCounter();
+ }
+ if (stage > 2) {
+ led_handler.DecrementRefCounter();
+ }
+ if (stage > 1) {
+ properties_handler.DecrementRefCounter();
+ }
+ return result;
+}
+
+Result AbstractPad::Deactivate() {
+ if (ref_counter == 0) {
+ return ResultNpadResourceNotInitialized;
+ }
+
+ ref_counter--;
+ battery_handler.DecrementRefCounter();
+ button_handler.DecrementRefCounter();
+ sixaxis_handler.DecrementRefCounter();
+ vibration_handler.DecrementRefCounter();
+ nfc_handler.DecrementRefCounter();
+ ir_sensor_handler.DecrementRefCounter();
+ mcu_handler.DecrementRefCounter();
+ led_handler.DecrementRefCounter();
+ properties_handler.DecrementRefCounter();
+ palma_handler.DecrementRefCounter();
+
+ return ResultSuccess;
+}
+
+Result AbstractPad::ActivateNpad(u64 aruid) {
+ Result result = ResultSuccess;
+ if (result.IsSuccess()) {
+ result = properties_handler.ActivateNpadUnknown0x88(aruid);
+ }
+ if (result.IsSuccess()) {
+ result = sixaxis_handler.UpdateSixAxisState2(aruid);
+ }
+ if (result.IsSuccess()) {
+ result = battery_handler.UpdateBatteryState(aruid);
+ }
+ return result;
+}
+
+NpadAbstractedPadHolder* AbstractPad::GetAbstractedPadHolder() {
+ return &abstract_pad_holder;
+}
+
+NpadAbstractPropertiesHandler* AbstractPad::GetAbstractPropertiesHandler() {
+ return &properties_handler;
+}
+
+NpadAbstractLedHandler* AbstractPad::GetAbstractLedHandler() {
+ return &led_handler;
+}
+
+NpadAbstractIrSensorHandler* AbstractPad::GetAbstractIrSensorHandler() {
+ return &ir_sensor_handler;
+}
+
+NpadAbstractMcuHandler* AbstractPad::GetAbstractMcuHandler() {
+ return &mcu_handler;
+}
+
+NpadAbstractNfcHandler* AbstractPad::GetAbstractNfcHandler() {
+ return &nfc_handler;
+}
+
+NpadAbstractVibrationHandler* AbstractPad::GetAbstractVibrationHandler() {
+ return &vibration_handler;
+}
+
+NpadAbstractSixAxisHandler* AbstractPad::GetAbstractSixAxisHandler() {
+ return &sixaxis_handler;
+}
+
+NpadAbstractButtonHandler* AbstractPad::GetAbstractButtonHandler() {
+ return &button_handler;
+}
+
+NpadAbstractBatteryHandler* AbstractPad::GetAbstractBatteryHandler() {
+ return &battery_handler;
+}
+
+NpadN64VibrationDevice* AbstractPad::GetN64VibrationDevice() {
+ return &vibration_n64;
+}
+
+NpadVibrationDevice* AbstractPad::GetVibrationDevice(Core::HID::DeviceIndex device_index) {
+ if (device_index == Core::HID::DeviceIndex::Right) {
+ return &vibration_right;
+ }
+ 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;
+}
+
+Core::HID::NpadIdType AbstractPad::GetLastActiveNpad() {
+ return properties_handler.GetNpadId();
+}
+
+void AbstractPad::UpdateInterfaceType() {
+ if (interface_type != properties_handler.GetInterfaceType()) {
+ Update();
+ }
+ battery_handler.UpdateBatteryState();
+}
+
+void AbstractPad::Update() {
+ properties_handler.UpdateDeviceType();
+ led_handler.SetNpadLedHandlerLedPattern();
+ vibration_handler.UpdateVibrationState();
+ sixaxis_handler.UpdateSixAxisState();
+ nfc_handler.UpdateNfcState();
+ ir_sensor_handler.UpdateIrSensorState();
+ mcu_handler.UpdateMcuState();
+ palma_handler.UpdatePalmaState();
+ battery_handler.UpdateBatteryState();
+ button_handler.EnableCenterClamp();
+
+ interface_type = properties_handler.GetInterfaceType();
+
+ std::scoped_lock lock{*applet_resource_holder->shared_mutex};
+ properties_handler.UpdateAllDeviceProperties();
+ battery_handler.UpdateCoreBatteryState();
+ button_handler.UpdateCoreBatteryState();
+}
+
+void AbstractPad::UpdatePadState() {
+ button_handler.UpdateAllButtonLifo();
+ sixaxis_handler.UpdateSixAxisState();
+ battery_handler.UpdateCoreBatteryState();
+}
+
+void AbstractPad::EnableAppletToGetInput(u64 aruid) {
+ button_handler.UpdateButtonState(aruid);
+ sixaxis_handler.UpdateSixAxisState(aruid);
+ battery_handler.UpdateBatteryState(aruid);
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.h b/src/hid_core/resources/abstracted_pad/abstract_pad.h
new file mode 100644
index 000000000..cbdf84af7
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+#include "hid_core/resources/abstracted_pad/abstract_battery_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_button_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_led_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_mcu_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_nfc_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_palma_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_vibration_handler.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 {
+class AppletResource;
+class SixAxisResource;
+class PalmaResource;
+class NPadResource;
+class AbstractPad;
+class NpadLastActiveHandler;
+class NpadIrNfcHandler;
+class UniquePads;
+class NpadPalmaHandler;
+class FirmwareResource;
+class NpadVibration;
+class NpadHighestBattery;
+class NpadGcVibration;
+
+class CaptureButtonResource;
+class HomeButtonResource;
+class VibrationHandler;
+
+struct HandheldConfig;
+
+/// Handles Npad request from HID interfaces
+class AbstractPad final {
+public:
+ explicit AbstractPad();
+ ~AbstractPad();
+
+ void SetExternals(AppletResourceHolder* applet_resource,
+ CaptureButtonResource* capture_button_resource,
+ HomeButtonResource* home_button_resource, SixAxisResource* sixaxis_resource,
+ PalmaResource* palma_resource, VibrationHandler* vibration);
+ void SetNpadId(Core::HID::NpadIdType npad_id);
+
+ Result Activate();
+ Result Deactivate();
+
+ Result ActivateNpad(u64 aruid);
+
+ NpadAbstractedPadHolder* GetAbstractedPadHolder();
+ NpadAbstractPropertiesHandler* GetAbstractPropertiesHandler();
+ NpadAbstractLedHandler* GetAbstractLedHandler();
+ NpadAbstractIrSensorHandler* GetAbstractIrSensorHandler();
+ NpadAbstractMcuHandler* GetAbstractMcuHandler();
+ NpadAbstractNfcHandler* GetAbstractNfcHandler();
+ NpadAbstractVibrationHandler* GetAbstractVibrationHandler();
+ NpadAbstractSixAxisHandler* GetAbstractSixAxisHandler();
+ NpadAbstractButtonHandler* GetAbstractButtonHandler();
+ NpadAbstractBatteryHandler* GetAbstractBatteryHandler();
+
+ NpadN64VibrationDevice* GetN64VibrationDevice();
+ NpadVibrationDevice* GetVibrationDevice(Core::HID::DeviceIndex device_index);
+ void GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list);
+ NpadGcVibrationDevice* GetGCVibrationDevice();
+
+ Core::HID::NpadIdType GetLastActiveNpad();
+ void UpdateInterfaceType();
+ void Update();
+
+ void UpdatePadState();
+ void EnableAppletToGetInput(u64 aruid);
+
+private:
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder abstract_pad_holder{};
+ NpadAbstractPropertiesHandler properties_handler{};
+ NpadAbstractLedHandler led_handler{};
+ NpadAbstractIrSensorHandler ir_sensor_handler{};
+ NpadAbstractNfcHandler nfc_handler{};
+ NpadAbstractMcuHandler mcu_handler{};
+ NpadAbstractVibrationHandler vibration_handler{};
+ NpadAbstractSixAxisHandler sixaxis_handler{};
+ NpadAbstractButtonHandler button_handler{};
+ NpadAbstractBatteryHandler battery_handler{};
+ NpadAbstractPalmaHandler palma_handler{};
+
+ NpadN64VibrationDevice vibration_n64{};
+ NpadVibrationDevice vibration_left{};
+ NpadVibrationDevice vibration_right{};
+ NpadGcVibrationDevice vibration_gc{};
+
+ // SixAxisConfigHolder fullkey_config;
+ // SixAxisConfigHolder handheld_config;
+ // SixAxisConfigHolder dual_left_config;
+ // SixAxisConfigHolder dual_right_config;
+ // SixAxisConfigHolder left_config;
+ // SixAxisConfigHolder right_config;
+
+ s32 ref_counter{};
+ Core::HID::NpadInterfaceType interface_type{Core::HID::NpadInterfaceType::None};
+};
+
+using FullAbstractPad = std::array<AbstractPad, MaxSupportedNpadIdTypes>;
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp
new file mode 100644
index 000000000..8334dc34f
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+Result NpadAbstractedPadHolder::RegisterAbstractPad(IAbstractedPad* abstracted_pad) {
+ if (list_size >= assignment_list.size()) {
+ return ResultNpadIsNotProController;
+ }
+
+ for (std::size_t i = 0; i < list_size; i++) {
+ if (assignment_list[i].device_type == abstracted_pad->device_type) {
+ return ResultNpadIsNotProController;
+ }
+ }
+
+ assignment_list[list_size] = {
+ .abstracted_pad = abstracted_pad,
+ .device_type = abstracted_pad->device_type,
+ .interface_type = abstracted_pad->interface_type,
+ .controller_id = abstracted_pad->controller_id,
+ };
+
+ list_size++;
+ return ResultSuccess;
+}
+
+void NpadAbstractedPadHolder::RemoveAbstractPadByControllerId(u64 controller_id) {
+ if (list_size == 0) {
+ return;
+ }
+ if (controller_id == 0) {
+ return;
+ }
+ for (std::size_t i = 0; i < list_size; i++) {
+ if (assignment_list[i].controller_id != controller_id) {
+ continue;
+ }
+ for (std::size_t e = i + 1; e < list_size; e++) {
+ assignment_list[e - 1] = assignment_list[e];
+ }
+ list_size--;
+ return;
+ }
+}
+
+void NpadAbstractedPadHolder::DetachAbstractedPad() {
+ while (list_size > 0) {
+ for (std::size_t i = 1; i < list_size; i++) {
+ assignment_list[i - 1] = assignment_list[i];
+ }
+ list_size--;
+ }
+}
+
+u64 NpadAbstractedPadHolder::RemoveAbstractPadByAssignmentStyle(
+ Service::HID::AssignmentStyle assignment_style) {
+ for (std::size_t i = 0; i < list_size; i++) {
+ if ((assignment_style.raw & assignment_list[i].abstracted_pad->assignment_style.raw) == 0) {
+ continue;
+ }
+ for (std::size_t e = i + 1; e < list_size; e++) {
+ assignment_list[e - 1] = assignment_list[e];
+ }
+ list_size--;
+ return list_size;
+ }
+ return list_size;
+}
+
+u32 NpadAbstractedPadHolder::GetAbstractedPads(std::span<IAbstractedPad*> list) const {
+ u32 num_elements = std::min(static_cast<u32>(list.size()), list_size);
+ for (std::size_t i = 0; i < num_elements; i++) {
+ list[i] = assignment_list[i].abstracted_pad;
+ }
+ return num_elements;
+}
+
+void NpadAbstractedPadHolder::SetAssignmentMode(const NpadJoyAssignmentMode& mode) {
+ assignment_mode = mode;
+}
+
+NpadJoyAssignmentMode NpadAbstractedPadHolder::GetAssignmentMode() const {
+ return assignment_mode;
+}
+
+std::size_t NpadAbstractedPadHolder::GetStyleIndexList(
+ std::span<Core::HID::NpadStyleIndex> list) const {
+ for (std::size_t i = 0; i < list_size; i++) {
+ list[i] = assignment_list[i].device_type;
+ }
+ return list_size;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h
new file mode 100644
index 000000000..fb7f472e8
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+struct IAbstractedPad;
+
+struct AbstractAssignmentHolder {
+ IAbstractedPad* abstracted_pad;
+ Core::HID::NpadStyleIndex device_type;
+ Core::HID::NpadInterfaceType interface_type;
+ INSERT_PADDING_BYTES(0x6);
+ u64 controller_id;
+};
+static_assert(sizeof(AbstractAssignmentHolder) == 0x18,
+ "AbstractAssignmentHolder is an invalid size");
+
+/// This is nn::hid::server::NpadAbstractedPadHolder
+class NpadAbstractedPadHolder final {
+public:
+ Result RegisterAbstractPad(IAbstractedPad* abstracted_pad);
+ void RemoveAbstractPadByControllerId(u64 controller_id);
+ void DetachAbstractedPad();
+ u64 RemoveAbstractPadByAssignmentStyle(Service::HID::AssignmentStyle assignment_style);
+ u32 GetAbstractedPads(std::span<IAbstractedPad*> list) const;
+
+ void SetAssignmentMode(const NpadJoyAssignmentMode& mode);
+ NpadJoyAssignmentMode GetAssignmentMode() const;
+
+ std::size_t GetStyleIndexList(std::span<Core::HID::NpadStyleIndex> list) const;
+
+private:
+ std::array<AbstractAssignmentHolder, 5> assignment_list{};
+ u32 list_size{};
+ NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp
new file mode 100644
index 000000000..04d276d61
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_palma_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+
+namespace Service::HID {
+
+NpadAbstractPalmaHandler::NpadAbstractPalmaHandler() {}
+
+NpadAbstractPalmaHandler::~NpadAbstractPalmaHandler() = default;
+
+void NpadAbstractPalmaHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractPalmaHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+ return;
+}
+
+void NpadAbstractPalmaHandler::SetPalmaResource(PalmaResource* resource) {
+ palma_resource = resource;
+}
+
+Result NpadAbstractPalmaHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractPalmaHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractPalmaHandler::UpdatePalmaState() {
+ // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h
new file mode 100644
index 000000000..fbd2e67e5
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+class PalmaResource;
+
+class NpadAbstractPalmaHandler final {
+public:
+ explicit NpadAbstractPalmaHandler();
+ ~NpadAbstractPalmaHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+ void SetPalmaResource(PalmaResource* resource);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void UpdatePalmaState();
+
+private:
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+ PalmaResource* palma_resource{nullptr};
+
+ s32 ref_counter{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp
new file mode 100644
index 000000000..4897a2784
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp
@@ -0,0 +1,322 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractPropertiesHandler::NpadAbstractPropertiesHandler() {}
+
+NpadAbstractPropertiesHandler::~NpadAbstractPropertiesHandler() = default;
+
+void NpadAbstractPropertiesHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+ return;
+}
+
+void NpadAbstractPropertiesHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+ return;
+}
+
+void NpadAbstractPropertiesHandler::SetNpadId(Core::HID::NpadIdType npad_id) {
+ if (!IsNpadIdValid(npad_id)) {
+ ASSERT_MSG(false, "Invalid npad id");
+ }
+
+ npad_id_type = npad_id;
+}
+
+Core::HID::NpadIdType NpadAbstractPropertiesHandler::GetNpadId() const {
+ return npad_id_type;
+}
+
+Result NpadAbstractPropertiesHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+
+ if (ref_counter != 0) {
+ ref_counter++;
+ return ResultSuccess;
+ }
+
+ const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+ for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+ auto& internal_state =
+ data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
+ if (!data->flag.is_assigned) {
+ continue;
+ }
+ internal_state.fullkey_lifo.buffer_count = 0;
+ internal_state.handheld_lifo.buffer_count = 0;
+ internal_state.joy_dual_lifo.buffer_count = 0;
+ internal_state.joy_left_lifo.buffer_count = 0;
+ internal_state.joy_right_lifo.buffer_count = 0;
+ internal_state.palma_lifo.buffer_count = 0;
+ internal_state.system_ext_lifo.buffer_count = 0;
+ internal_state.gc_trigger_lifo.buffer_count = 0;
+ internal_state.sixaxis_fullkey_lifo.lifo.buffer_count = 0;
+ internal_state.sixaxis_handheld_lifo.lifo.buffer_count = 0;
+ internal_state.sixaxis_dual_left_lifo.lifo.buffer_count = 0;
+ internal_state.sixaxis_dual_right_lifo.lifo.buffer_count = 0;
+ internal_state.sixaxis_left_lifo.lifo.buffer_count = 0;
+ internal_state.sixaxis_right_lifo.lifo.buffer_count = 0;
+
+ internal_state.style_tag = {Core::HID::NpadStyleSet::None};
+ internal_state.assignment_mode = NpadJoyAssignmentMode::Dual;
+ internal_state.joycon_color = {};
+ internal_state.fullkey_color = {};
+
+ internal_state.system_properties.raw = 0;
+ internal_state.button_properties.raw = 0;
+ internal_state.device_type.raw = 0;
+
+ internal_state.battery_level_dual = Core::HID::NpadBatteryLevel::Empty;
+ internal_state.battery_level_left = Core::HID::NpadBatteryLevel::Empty;
+ internal_state.battery_level_right = Core::HID::NpadBatteryLevel::Empty;
+
+ internal_state.applet_footer_type = AppletFooterUiType::None;
+ internal_state.applet_footer_attributes = {};
+ internal_state.lark_type_l_and_main = {};
+ internal_state.lark_type_r = {};
+
+ internal_state.sixaxis_fullkey_properties.is_newly_assigned.Assign(true);
+ internal_state.sixaxis_handheld_properties.is_newly_assigned.Assign(true);
+ internal_state.sixaxis_dual_left_properties.is_newly_assigned.Assign(true);
+ internal_state.sixaxis_dual_right_properties.is_newly_assigned.Assign(true);
+ internal_state.sixaxis_left_properties.is_newly_assigned.Assign(true);
+ internal_state.sixaxis_right_properties.is_newly_assigned.Assign(true);
+ }
+
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractPropertiesHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+Result NpadAbstractPropertiesHandler::ActivateNpadUnknown0x88(u64 aruid) {
+ const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+ for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+ if (!data->flag.is_assigned || data->aruid != aruid) {
+ continue;
+ }
+ UpdateDeviceProperties(aruid, data->shared_memory_format->npad.npad_entry[npad_index]);
+ return ResultSuccess;
+ }
+ return ResultSuccess;
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceType() {
+ // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceColor() {
+ // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateFooterAttributes() {
+ // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateAllDeviceProperties() {
+ const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+ for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+ if (!data->flag.is_assigned) {
+ continue;
+ }
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[npad_index];
+ UpdateDeviceProperties(data->aruid, npad_entry);
+ }
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetFullkeyInterfaceType() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (abstract_pad->device_type != Core::HID::NpadStyleIndex::Fullkey) {
+ continue;
+ }
+ if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+ // Abort
+ continue;
+ }
+ return abstract_pad->interface_type;
+ }
+
+ return Core::HID::NpadInterfaceType::None;
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetInterfaceType() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (!abstract_pad->disabled_feature_set.has_identification_code) {
+ continue;
+ }
+ if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+ // Abort
+ continue;
+ }
+ return abstract_pad->interface_type;
+ }
+ return Core::HID::NpadInterfaceType::None;
+}
+
+Core::HID::NpadStyleSet NpadAbstractPropertiesHandler::GetStyleSet(u64 aruid) {
+ // TODO
+ return Core::HID::NpadStyleSet::None;
+}
+
+std::size_t NpadAbstractPropertiesHandler::GetAbstractedPadsWithStyleTag(
+ std::span<IAbstractedPad*> list, Core::HID::NpadStyleTag style) {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ if (count == 0) {
+ return count;
+ }
+
+ bool is_supported_style_set{};
+ const auto result = applet_resource_holder->shared_npad_resource->IsSupportedNpadStyleSet(
+ is_supported_style_set, applet_resource_holder->applet_resource->GetActiveAruid());
+
+ if (!is_supported_style_set || result.IsError()) {
+ for (std::size_t i = 0; i < count; i++) {
+ // TODO
+ }
+ return count;
+ }
+
+ std::size_t filtered_count{};
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ const bool is_enabled = true;
+ if (is_enabled) {
+ list[filtered_count] = abstract_pad;
+ filtered_count++;
+ }
+ }
+
+ return filtered_count;
+}
+
+std::size_t NpadAbstractPropertiesHandler::GetAbstractedPads(std::span<IAbstractedPad*> list) {
+ Core::HID::NpadStyleTag style{
+ GetStyleSet(applet_resource_holder->applet_resource->GetActiveAruid())};
+ return GetAbstractedPadsWithStyleTag(list, style);
+}
+
+AppletFooterUiType NpadAbstractPropertiesHandler::GetAppletFooterUiType() {
+ return applet_ui_type.footer;
+}
+
+AppletDetailedUiType NpadAbstractPropertiesHandler::GetAppletDetailedUiType() {
+ return applet_ui_type;
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceProperties(u64 aruid,
+ NpadSharedMemoryEntry& internal_state) {
+ // TODO
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetNpadInterfaceType() {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+ // Abort
+ continue;
+ }
+ return abstract_pad->interface_type;
+ }
+
+ return Core::HID::NpadInterfaceType::None;
+}
+
+Result NpadAbstractPropertiesHandler::GetNpadFullKeyGripColor(
+ Core::HID::NpadColor& main_color, Core::HID::NpadColor& sub_color) const {
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ if (applet_ui_type.footer != AppletFooterUiType::SwitchProController) {
+ return ResultNpadIsNotProController;
+ }
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ return ResultSuccess;
+ }
+
+ return ResultNpadIsNotProController;
+}
+
+void NpadAbstractPropertiesHandler::GetNpadLeftRightInterfaceType(
+ Core::HID::NpadInterfaceType& out_left_interface,
+ Core::HID::NpadInterfaceType& out_right_interface) const {
+ out_left_interface = Core::HID::NpadInterfaceType::None;
+ out_right_interface = Core::HID::NpadInterfaceType::None;
+
+ std::array<IAbstractedPad*, 5> abstract_pads{};
+ const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+ for (std::size_t i = 0; i < count; i++) {
+ auto* abstract_pad = abstract_pads[i];
+ if (!abstract_pad->internal_flags.is_connected) {
+ continue;
+ }
+ if (abstract_pad->assignment_style.is_external_left_assigned &&
+ abstract_pad->assignment_style.is_handheld_left_assigned) {
+ if (abstract_pad->interface_type > Core::HID::NpadInterfaceType::Embedded) {
+ // Abort
+ continue;
+ }
+ out_left_interface = abstract_pad->interface_type;
+ continue;
+ }
+ if (abstract_pad->assignment_style.is_external_right_assigned &&
+ abstract_pad->assignment_style.is_handheld_right_assigned) {
+ if (abstract_pad->interface_type > Core::HID::NpadInterfaceType::Embedded) {
+ // Abort
+ continue;
+ }
+ out_right_interface = abstract_pad->interface_type;
+ continue;
+ }
+ }
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h
new file mode 100644
index 000000000..fa6827899
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+struct NpadSharedMemoryEntry;
+
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+
+struct ColorProperties {
+ ColorAttribute attribute;
+ Core::HID::NpadControllerColor color;
+ INSERT_PADDING_BYTES(0x4);
+};
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractPropertiesHandler final {
+public:
+ explicit NpadAbstractPropertiesHandler();
+ ~NpadAbstractPropertiesHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetNpadId(Core::HID::NpadIdType npad_id);
+
+ Core::HID::NpadIdType GetNpadId() const;
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ Result ActivateNpadUnknown0x88(u64 aruid);
+
+ void UpdateDeviceType();
+ void UpdateDeviceColor();
+ void UpdateFooterAttributes();
+ void UpdateAllDeviceProperties();
+
+ Core::HID::NpadInterfaceType GetFullkeyInterfaceType();
+ Core::HID::NpadInterfaceType GetInterfaceType();
+
+ Core::HID::NpadStyleSet GetStyleSet(u64 aruid);
+ std::size_t GetAbstractedPadsWithStyleTag(std::span<IAbstractedPad*> list,
+ Core::HID::NpadStyleTag style);
+ std::size_t GetAbstractedPads(std::span<IAbstractedPad*> list);
+
+ AppletFooterUiType GetAppletFooterUiType();
+
+ AppletDetailedUiType GetAppletDetailedUiType();
+
+ void UpdateDeviceProperties(u64 aruid, NpadSharedMemoryEntry& internal_state);
+
+ Core::HID::NpadInterfaceType GetNpadInterfaceType();
+
+ Result GetNpadFullKeyGripColor(Core::HID::NpadColor& main_color,
+ Core::HID::NpadColor& sub_color) const;
+
+ void GetNpadLeftRightInterfaceType(Core::HID::NpadInterfaceType& param_2,
+ Core::HID::NpadInterfaceType& param_3) const;
+
+private:
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ Core::HID::NpadIdType npad_id_type{Core::HID::NpadIdType::Invalid};
+ s32 ref_counter{};
+ Core::HID::DeviceIndex device_type{};
+ AppletDetailedUiType applet_ui_type{};
+ AppletFooterUiAttributes applet_ui_attributes{};
+ bool is_vertical{};
+ bool is_horizontal{};
+ bool use_plus{};
+ bool use_minus{};
+ bool has_directional_buttons{};
+ ColorProperties fullkey_color{};
+ ColorProperties left_color{};
+ ColorProperties right_color{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp
new file mode 100644
index 000000000..6d759298e
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp
@@ -0,0 +1,154 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractSixAxisHandler::NpadAbstractSixAxisHandler() {}
+
+NpadAbstractSixAxisHandler::~NpadAbstractSixAxisHandler() = default;
+
+void NpadAbstractSixAxisHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractSixAxisHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractSixAxisHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+void NpadAbstractSixAxisHandler::SetSixaxisResource(SixAxisResource* resource) {
+ six_axis_resource = resource;
+}
+
+Result NpadAbstractSixAxisHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+u64 NpadAbstractSixAxisHandler::IsFirmwareUpdateAvailable() {
+ // TODO
+ return false;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState() {
+ Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ for (std::size_t i = 0; i < AruidIndexMax; i++) {
+ auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+ if (data->flag.is_assigned) {
+ continue;
+ }
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateSixaxisInternalState(npad_entry, data->aruid,
+ data->flag.enable_six_axis_sensor.As<bool>());
+ }
+ return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState(u64 aruid) {
+ Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+ auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+ if (data == nullptr) {
+ return ResultSuccess;
+ }
+ auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+ UpdateSixaxisInternalState(npad_entry, data->aruid,
+ data->flag.enable_six_axis_sensor.As<bool>());
+ return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState2(u64 aruid) {
+ const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId());
+ AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+ if (aruid_data == nullptr) {
+ return ResultSuccess;
+ }
+ auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index];
+ UpdateSixaxisInternalState(npad_internal_state, aruid,
+ aruid_data->flag.enable_six_axis_sensor.As<bool>());
+ return ResultSuccess;
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisInternalState(NpadSharedMemoryEntry& npad_entry,
+ u64 aruid, bool is_sensor_enabled) {
+ const Core::HID::NpadStyleTag style_tag{properties_handler->GetStyleSet(aruid)};
+
+ if (!style_tag.palma) {
+ UpdateSixaxisFullkeyLifo(style_tag, npad_entry.internal_state.sixaxis_fullkey_lifo,
+ is_sensor_enabled);
+ } else {
+ UpdateSixAxisPalmaLifo(style_tag, npad_entry.internal_state.sixaxis_fullkey_lifo,
+ is_sensor_enabled);
+ }
+ UpdateSixaxisHandheldLifo(style_tag, npad_entry.internal_state.sixaxis_handheld_lifo,
+ is_sensor_enabled);
+ UpdateSixaxisDualLifo(style_tag, npad_entry.internal_state.sixaxis_dual_left_lifo,
+ is_sensor_enabled);
+ UpdateSixaxisDualLifo(style_tag, npad_entry.internal_state.sixaxis_dual_right_lifo,
+ is_sensor_enabled);
+ UpdateSixaxisLeftLifo(style_tag, npad_entry.internal_state.sixaxis_left_lifo,
+ is_sensor_enabled);
+ UpdateSixaxisRightLifo(style_tag, npad_entry.internal_state.sixaxis_right_lifo,
+ is_sensor_enabled);
+ // TODO: Set sixaxis properties
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixAxisPalmaLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisHandheldLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisDualLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisLeftLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisRightLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo,
+ bool is_sensor_enabled) {
+ // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h
new file mode 100644
index 000000000..9c20459e9
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+class SixAxisResource;
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+struct NpadSixAxisSensorLifo;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractSixAxisHandler final {
+public:
+ explicit NpadAbstractSixAxisHandler();
+ ~NpadAbstractSixAxisHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+ void SetSixaxisResource(SixAxisResource* resource);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ u64 IsFirmwareUpdateAvailable();
+
+ Result UpdateSixAxisState();
+ Result UpdateSixAxisState(u64 aruid);
+ Result UpdateSixAxisState2(u64 aruid);
+
+private:
+ void UpdateSixaxisInternalState(NpadSharedMemoryEntry& npad_entry, u64 aruid,
+ bool is_sensor_enabled);
+ void UpdateSixaxisFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+ void UpdateSixAxisPalmaLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+ void UpdateSixaxisHandheldLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+ void UpdateSixaxisDualLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+ void UpdateSixaxisLeftLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+ void UpdateSixaxisRightLifo(Core::HID::NpadStyleTag style_tag,
+ NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+ SixAxisResource* six_axis_resource{nullptr};
+
+ s32 ref_counter{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp
new file mode 100644
index 000000000..a00d6c9de
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_vibration_handler.h"
+#include "hid_core/resources/applet_resource.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_device.h"
+
+namespace Service::HID {
+
+NpadAbstractVibrationHandler::NpadAbstractVibrationHandler() {}
+
+NpadAbstractVibrationHandler::~NpadAbstractVibrationHandler() = default;
+
+void NpadAbstractVibrationHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+ abstract_pad_holder = holder;
+}
+
+void NpadAbstractVibrationHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+ applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractVibrationHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+ properties_handler = handler;
+}
+
+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::SetGcVibration(NpadGcVibrationDevice* gc_device) {
+ gc_vibration_device = gc_device;
+}
+
+Result NpadAbstractVibrationHandler::IncrementRefCounter() {
+ if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+ return ResultNpadHandlerOverflow;
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadAbstractVibrationHandler::DecrementRefCounter() {
+ if (ref_counter == 0) {
+ return ResultNpadHandlerNotInitialized;
+ }
+ ref_counter--;
+ return ResultSuccess;
+}
+
+void NpadAbstractVibrationHandler::UpdateVibrationState() {
+ const bool is_handheld_hid_enabled =
+ applet_resource_holder->handheld_config->is_handheld_hid_enabled;
+ const bool is_force_handheld_style_vibration =
+ applet_resource_holder->handheld_config->is_force_handheld_style_vibration;
+
+ if (!is_handheld_hid_enabled && is_force_handheld_style_vibration) {
+ // TODO
+ }
+}
+} // 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
new file mode 100644
index 000000000..aeb07ce86
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+class NpadGcVibrationDevice;
+class NpadVibrationDevice;
+class NpadN64VibrationDevice;
+class NpadVibration;
+
+/// Keeps track of battery levels and updates npad battery shared memory values
+class NpadAbstractVibrationHandler final {
+public:
+ explicit NpadAbstractVibrationHandler();
+ ~NpadAbstractVibrationHandler();
+
+ void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+ void SetAppletResource(AppletResourceHolder* applet_resource);
+ void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+ void SetN64Vibration(NpadN64VibrationDevice* n64_device);
+ void SetVibration(std::span<NpadVibrationDevice*> device);
+ void SetGcVibration(NpadGcVibrationDevice* gc_device);
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ void UpdateVibrationState();
+
+private:
+ AppletResourceHolder* applet_resource_holder{nullptr};
+ NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+ NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+ NpadN64VibrationDevice* n64_vibration_device{nullptr};
+ std::array<NpadVibrationDevice*, 2> vibration_device{};
+ NpadGcVibrationDevice* gc_vibration_device{nullptr};
+ NpadVibration* vibration_handler{nullptr};
+ s32 ref_counter{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp
index 1f8a0f8ab..97537a2e2 100644
--- a/src/hid_core/resources/npad/npad.cpp
+++ b/src/hid_core/resources/npad/npad.cpp
@@ -193,7 +193,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
case Core::HID::NpadStyleIndex::None:
ASSERT(false);
break;
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
shared_memory->fullkey_color.fullkey = body_colors.fullkey;
shared_memory->battery_level_dual = battery_level.dual.battery_level;
@@ -491,7 +491,7 @@ void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
case Core::HID::NpadStyleIndex::None:
ASSERT(false);
break;
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::NES:
case Core::HID::NpadStyleIndex::SNES:
case Core::HID::NpadStyleIndex::N64:
@@ -1292,7 +1292,7 @@ Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
auto& controller = GetControllerFromHandle(aruid, sixaxis_handle);
switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Pokeball:
return controller.shared_memory->sixaxis_fullkey_properties;
case Core::HID::NpadStyleIndex::Handheld:
@@ -1315,7 +1315,7 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
const auto& controller = GetControllerFromHandle(aruid, sixaxis_handle);
switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Pokeball:
return controller.shared_memory->sixaxis_fullkey_properties;
case Core::HID::NpadStyleIndex::Handheld:
diff --git a/src/hid_core/resources/npad/npad_data.cpp b/src/hid_core/resources/npad/npad_data.cpp
index c7e9760cb..29ad5cb08 100644
--- a/src/hid_core/resources/npad/npad_data.cpp
+++ b/src/hid_core/resources/npad/npad_data.cpp
@@ -151,7 +151,7 @@ Core::HID::NpadStyleSet NPadData::GetSupportedNpadStyleSet() const {
bool NPadData::IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index) const {
Core::HID::NpadStyleTag style = {supported_npad_style_set};
switch (style_index) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
return style.fullkey.As<bool>();
case Core::HID::NpadStyleIndex::Handheld:
return style.handheld.As<bool>();
diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h
index a02f9cf16..074dd40cf 100644
--- a/src/hid_core/resources/npad/npad_types.h
+++ b/src/hid_core/resources/npad/npad_types.h
@@ -252,4 +252,103 @@ enum class NpadLagerType : u32 {
U,
};
+// nn::hidtypes::FeatureType
+struct FeatureType {
+ union {
+ u64 raw{};
+ BitField<0, 1, u64> has_left_analog_stick;
+ BitField<1, 1, u64> has_right_analog_stick;
+ BitField<2, 1, u64> has_left_joy_six_axis_sensor;
+ BitField<3, 1, u64> has_right_joy_six_axis_sensor;
+ BitField<4, 1, u64> has_fullkey_joy_six_axis_sensor;
+ BitField<5, 1, u64> has_left_lra_vibration_device;
+ BitField<6, 1, u64> has_right_lra_vibration_device;
+ BitField<7, 1, u64> has_gc_vibration_device;
+ BitField<8, 1, u64> has_erm_vibration_device;
+ BitField<9, 1, u64> has_left_joy_rail_bus;
+ BitField<10, 1, u64> has_right_joy_rail_bus;
+ BitField<11, 1, u64> has_internal_bus;
+ BitField<12, 1, u64> is_palma;
+ BitField<13, 1, u64> has_nfc;
+ BitField<14, 1, u64> has_ir_sensor;
+ BitField<15, 1, u64> is_analog_stick_calibration_supported;
+ BitField<16, 1, u64> is_six_axis_Sensor_user_calibration_supported;
+ BitField<17, 1, u64> has_left_right_joy_battery;
+ BitField<18, 1, u64> has_fullkey_battery;
+ BitField<19, 1, u64> is_disconnect_controller_if_battery_none;
+ BitField<20, 1, u64> has_controller_color;
+ BitField<21, 1, u64> has_grip_color;
+ BitField<22, 1, u64> has_identification_code;
+ BitField<23, 1, u64> has_bluetooth_address;
+ BitField<24, 1, u64> has_mcu;
+ BitField<25, 1, u64> has_notification_led;
+ BitField<26, 1, u64> has_directional_buttons;
+ BitField<27, 1, u64> has_indicator_led;
+ BitField<28, 1, u64> is_button_config_embedded_supported;
+ BitField<29, 1, u64> is_button_config_full_supported;
+ BitField<30, 1, u64> is_button_config_left_supported;
+ BitField<31, 1, u64> is_button_config_right_supported;
+ BitField<32, 1, u64> is_usb_hid_device;
+ BitField<33, 1, u64> is_kuina_device;
+ BitField<34, 1, u64> is_direct_usb_to_bt_switching_device;
+ BitField<35, 1, u64> is_normalize_analog_stick_with_inner_cross;
+ };
+};
+static_assert(sizeof(FeatureType) == 8, "FeatureType is an invalid size");
+
+// This is nn::hid::AssignmentStyle
+struct AssignmentStyle {
+ union {
+ u32 raw{};
+ BitField<0, 1, u32> is_external_assigned;
+ BitField<1, 1, u32> is_external_left_assigned;
+ BitField<2, 1, u32> is_external_right_assigned;
+ BitField<3, 1, u32> is_handheld_assigned;
+ BitField<4, 1, u32> is_handheld_left_assigned;
+ BitField<5, 1, u32> is_handheld_right_assigned;
+ };
+};
+static_assert(sizeof(AssignmentStyle) == 4, "AssignmentStyle is an invalid size");
+
+// This is nn::hid::server::IAbstractedPad::InternalFlags
+struct InternalFlags {
+ union {
+ u32 raw{};
+ BitField<0, 1, u32> is_bound;
+ BitField<1, 1, u32> is_connected;
+ BitField<2, 1, u32> is_battery_low_ovln_required;
+ BitField<3, 1, u32> is_battery_low_ovln_delay_required;
+ BitField<4, 1, u32> is_sample_recieved;
+ BitField<5, 1, u32> is_virtual_input;
+ BitField<6, 1, u32> is_wired;
+ BitField<8, 1, u32> use_center_clamp;
+ BitField<9, 1, u32> has_virtual_six_axis_sensor_acceleration;
+ BitField<10, 1, u32> has_virtual_six_axis_sensor_angle;
+ BitField<11, 1, u32> is_debug_pad;
+ };
+};
+static_assert(sizeof(InternalFlags) == 4, "InternalFlags is an invalid size");
+
+/// This is nn::hid::server::IAbstractedPad
+struct IAbstractedPad {
+ InternalFlags internal_flags;
+ u64 controller_id;
+ u32 controller_number;
+ u64 low_battery_display_delay_time;
+ u64 low_battery_display_delay_interval;
+ FeatureType feature_set;
+ FeatureType disabled_feature_set;
+ AssignmentStyle assignment_style;
+ Core::HID::NpadStyleIndex device_type;
+ Core::HID::NpadInterfaceType interface_type;
+ Core::HID::NpadPowerInfo power_info;
+ u32 pad_state;
+ u32 button_mask;
+ u32 system_button_mask;
+ u8 indicator;
+ std::vector<f32> virtual_six_axis_sensor_acceleration;
+ std::vector<f32> virtual_six_axis_sensor_angle;
+ u64 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
new file mode 100644
index 000000000..3bdd55dec
--- /dev/null
+++ b/src/hid_core/resources/npad/npad_vibration.cpp
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+
+namespace Service::HID {
+
+NpadVibration::NpadVibration() {}
+
+NpadVibration::~NpadVibration() = default;
+
+Result NpadVibration::Activate() {
+ std::scoped_lock lock{mutex};
+
+ const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+ // if (master_volume < 0.0f || master_volume > 1.0f) {
+ // return ResultVibrationStrenghtOutOfRange;
+ // }
+
+ volume = master_volume;
+ return ResultSuccess;
+}
+
+Result NpadVibration::Deactivate() {
+ return ResultSuccess;
+}
+
+Result NpadVibration::SetVibrationMasterVolume(f32 master_volume) {
+ std::scoped_lock lock{mutex};
+
+ if (master_volume < 0.0f && master_volume > 1.0f) {
+ return ResultVibrationStrenghtOutOfRange;
+ }
+
+ volume = master_volume;
+ // nn::settings::system::SetVibrationMasterVolume(master_volume);
+
+ return ResultSuccess;
+}
+
+Result NpadVibration::GetVibrationVolume(f32& out_volume) const {
+ std::scoped_lock lock{mutex};
+ out_volume = volume;
+ return ResultSuccess;
+}
+
+Result NpadVibration::GetVibrationMasterVolume(f32& out_volume) const {
+ std::scoped_lock lock{mutex};
+
+ const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+ // if (master_volume < 0.0f || master_volume > 1.0f) {
+ // return ResultVibrationStrenghtOutOfRange;
+ // }
+
+ out_volume = master_volume;
+ return ResultSuccess;
+}
+
+Result NpadVibration::BeginPermitVibrationSession(u64 aruid) {
+ std::scoped_lock lock{mutex};
+ session_aruid = aruid;
+ volume = 1.0;
+ return ResultSuccess;
+}
+
+Result NpadVibration::EndPermitVibrationSession() {
+ std::scoped_lock lock{mutex};
+
+ const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+ // if (master_volume < 0.0f || master_volume > 1.0f) {
+ // return ResultVibrationStrenghtOutOfRange;
+ // }
+
+ volume = master_volume;
+ session_aruid = 0;
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/npad/npad_vibration.h b/src/hid_core/resources/npad/npad_vibration.h
new file mode 100644
index 000000000..0748aeffc
--- /dev/null
+++ b/src/hid_core/resources/npad/npad_vibration.h
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Service::HID {
+
+class NpadVibration final {
+public:
+ explicit NpadVibration();
+ ~NpadVibration();
+
+ Result Activate();
+ Result Deactivate();
+
+ Result SetVibrationMasterVolume(f32 master_volume);
+ Result GetVibrationVolume(f32& out_volume) const;
+ Result GetVibrationMasterVolume(f32& out_volume) const;
+
+ Result BeginPermitVibrationSession(u64 aruid);
+ Result EndPermitVibrationSession();
+
+private:
+ f32 volume{};
+ u64 session_aruid{};
+ mutable std::mutex mutex;
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/six_axis/six_axis.cpp b/src/hid_core/resources/six_axis/six_axis.cpp
index 8a9677c50..da12d2d5a 100644
--- a/src/hid_core/resources/six_axis/six_axis.cpp
+++ b/src/hid_core/resources/six_axis/six_axis.cpp
@@ -114,7 +114,7 @@ void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
case Core::HID::NpadStyleIndex::None:
ASSERT(false);
break;
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
set_motion_state(sixaxis_fullkey_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -345,7 +345,7 @@ SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
auto& controller = GetControllerFromHandle(sixaxis_handle);
switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Pokeball:
return controller.sixaxis_fullkey;
case Core::HID::NpadStyleIndex::Handheld:
@@ -368,7 +368,7 @@ const SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
const auto& controller = GetControllerFromHandle(sixaxis_handle);
switch (sixaxis_handle.npad_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Pokeball:
return controller.sixaxis_fullkey;
case Core::HID::NpadStyleIndex::Handheld:
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.cpp b/src/hid_core/resources/vibration/gc_vibration_device.cpp
new file mode 100644
index 000000000..f01f81b9a
--- /dev/null
+++ b/src/hid_core/resources/vibration/gc_vibration_device.cpp
@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.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"
+
+namespace Service::HID {
+
+NpadGcVibrationDevice::NpadGcVibrationDevice() {}
+
+Result NpadGcVibrationDevice::IncrementRefCounter() {
+ if (ref_counter == 0 && is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ // TODO: SendVibrationGcErmCommand
+ }
+ }
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::DecrementRefCounter() {
+ if (ref_counter == 1 && !is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ // TODO: SendVibrationGcErmCommand
+ }
+ }
+
+ if (ref_counter > 0) {
+ ref_counter--;
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command) {
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume == 0.0) {
+ command = Core::HID::VibrationGcErmCommand::Stop;
+ } else {
+ if (command > Core::HID::VibrationGcErmCommand::StopHard) {
+ // Abort
+ return ResultSuccess;
+ }
+ }
+ // TODO: SendVibrationGcErmCommand
+ return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::GetActualVibrationGcErmCommand(
+ Core::HID::VibrationGcErmCommand& out_command) {
+ if (!is_mounted) {
+ out_command = Core::HID::VibrationGcErmCommand::Stop;
+ return ResultSuccess;
+ }
+
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume == 0.0f) {
+ out_command = Core::HID::VibrationGcErmCommand::Stop;
+ return ResultSuccess;
+ }
+
+ // TODO: GetActualVibrationGcErmCommand
+ return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::SendVibrationNotificationPattern(
+ Core::HID::VibrationGcErmCommand command) {
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume <= 0.0f) {
+ command = Core::HID::VibrationGcErmCommand::Stop;
+ }
+ if (command > Core::HID::VibrationGcErmCommand::StopHard) {
+ // Abort
+ return ResultSuccess;
+ }
+
+ // TODO: SendVibrationNotificationPattern
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.h b/src/hid_core/resources/vibration/gc_vibration_device.h
new file mode 100644
index 000000000..87abca57d
--- /dev/null
+++ b/src/hid_core/resources/vibration/gc_vibration_device.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadGcVibrationDevice final : public NpadVibrationBase {
+public:
+ explicit NpadGcVibrationDevice();
+
+ Result IncrementRefCounter() override;
+ Result DecrementRefCounter() override;
+
+ Result SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command);
+
+ Result GetActualVibrationGcErmCommand(Core::HID::VibrationGcErmCommand& out_command);
+ Result SendVibrationNotificationPattern(Core::HID::VibrationGcErmCommand command);
+};
+} // 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
new file mode 100644
index 000000000..639f87abf
--- /dev/null
+++ b/src/hid_core/resources/vibration/n64_vibration_device.cpp
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+
+namespace Service::HID {
+
+NpadN64VibrationDevice::NpadN64VibrationDevice() {}
+
+Result NpadN64VibrationDevice::IncrementRefCounter() {
+ if (ref_counter == 0 && is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ // TODO: SendVibrationInBool
+ }
+ }
+
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::DecrementRefCounter() {
+ if (ref_counter == 1) {
+ if (!is_mounted) {
+ ref_counter = 0;
+ if (is_mounted != false) {
+ // TODO: SendVibrationInBool
+ }
+ return ResultSuccess;
+ }
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsSuccess()) {
+ // TODO
+ }
+ }
+
+ if (ref_counter > 0) {
+ ref_counter--;
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) {
+ if (ref_counter < 1) {
+ return ResultVibrationNotInitialized;
+ }
+ if (is_mounted) {
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ // TODO: SendVibrationInBool
+ }
+ return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u32 pattern) {
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume <= 0.0) {
+ pattern = 0;
+ }
+ // TODO: SendVibrationNotificationPattern
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/n64_vibration_device.h b/src/hid_core/resources/vibration/n64_vibration_device.h
new file mode 100644
index 000000000..54e6efc1a
--- /dev/null
+++ b/src/hid_core/resources/vibration/n64_vibration_device.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadN64VibrationDevice final : public NpadVibrationBase {
+public:
+ explicit NpadN64VibrationDevice();
+
+ Result IncrementRefCounter() override;
+ Result DecrementRefCounter() override;
+
+ Result SendValueInBool(bool is_vibrating);
+ Result SendVibrationNotificationPattern(u32 pattern);
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_base.cpp b/src/hid_core/resources/vibration/vibration_base.cpp
new file mode 100644
index 000000000..350f349c2
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_base.cpp
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+
+NpadVibrationBase::NpadVibrationBase() {}
+
+Result NpadVibrationBase::IncrementRefCounter() {
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadVibrationBase::DecrementRefCounter() {
+ if (ref_counter > 0) {
+ ref_counter--;
+ }
+
+ return ResultSuccess;
+}
+
+bool NpadVibrationBase::IsVibrationMounted() const {
+ return is_mounted;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_base.h b/src/hid_core/resources/vibration/vibration_base.h
new file mode 100644
index 000000000..c6c5fc4d9
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_base.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadVibrationBase {
+public:
+ explicit NpadVibrationBase();
+
+ virtual Result IncrementRefCounter();
+ virtual Result DecrementRefCounter();
+
+ bool IsVibrationMounted() const;
+
+protected:
+ u64 xcd_handle{};
+ s32 ref_counter{};
+ bool is_mounted{};
+ NpadVibration* vibration_handler{nullptr};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_device.cpp b/src/hid_core/resources/vibration/vibration_device.cpp
new file mode 100644
index 000000000..888c3a7ed
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_device.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/vibration_device.h"
+
+namespace Service::HID {
+
+NpadVibrationDevice::NpadVibrationDevice() {}
+
+Result NpadVibrationDevice::IncrementRefCounter() {
+ ref_counter++;
+ return ResultSuccess;
+}
+
+Result NpadVibrationDevice::DecrementRefCounter() {
+ if (ref_counter > 0) {
+ ref_counter--;
+ }
+
+ return ResultSuccess;
+}
+
+Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& value) {
+ if (ref_counter == 0) {
+ return ResultVibrationNotInitialized;
+ }
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume <= 0.0f) {
+ // TODO: SendVibrationValue
+ return ResultSuccess;
+ }
+
+ Core::HID::VibrationValue vibration_value = value;
+ vibration_value.high_amplitude *= volume;
+ vibration_value.low_amplitude *= volume;
+
+ // TODO: SendVibrationValue
+ return ResultSuccess;
+}
+
+Result NpadVibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u32 pattern) {
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+
+ f32 volume = 1.0f;
+ const auto result = vibration_handler->GetVibrationVolume(volume);
+ if (result.IsError()) {
+ return result;
+ }
+ if (volume <= 0.0) {
+ pattern = 0;
+ }
+
+ // return xcd_handle->SendVibrationNotificationPattern(pattern);
+ return ResultSuccess;
+}
+
+Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) {
+ if (ref_counter < 1) {
+ return ResultVibrationNotInitialized;
+ }
+
+ out_value = Core::HID::DEFAULT_VIBRATION_VALUE;
+ if (!is_mounted) {
+ return ResultSuccess;
+ }
+
+ // TODO: SendVibrationValue
+ return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_device.h b/src/hid_core/resources/vibration/vibration_device.h
new file mode 100644
index 000000000..3574ad60b
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_device.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadVibrationDevice final : public NpadVibrationBase {
+public:
+ explicit NpadVibrationDevice();
+
+ Result IncrementRefCounter();
+ Result DecrementRefCounter();
+
+ Result SendVibrationValue(const Core::HID::VibrationValue& value);
+ Result SendVibrationNotificationPattern(u32 pattern);
+
+ Result GetActualVibrationValue(Core::HID::VibrationValue& out_value);
+
+private:
+ u32 device_index{};
+};
+
+} // namespace Service::HID
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
index 8693801c7..bdcbccfde 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
@@ -65,6 +65,14 @@ void WriteStorage32(EmitContext& ctx, const IR::Value& binding, const IR::Value&
WriteStorage(ctx, binding, offset, value, ctx.storage_types.U32, sizeof(u32),
&StorageDefinitions::U32, index_offset);
}
+
+void WriteStorageByCasLoop(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
+ Id value, Id bit_offset, Id bit_count) {
+ const Id pointer{StoragePointer(ctx, binding, offset, ctx.storage_types.U32, sizeof(u32),
+ &StorageDefinitions::U32)};
+ ctx.OpFunctionCall(ctx.TypeVoid(), ctx.write_storage_cas_loop_func, pointer, value, bit_offset,
+ bit_count);
+}
} // Anonymous namespace
void EmitLoadGlobalU8(EmitContext&) {
@@ -219,26 +227,42 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu
void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) {
- WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8,
- sizeof(u8), &StorageDefinitions::U8);
+ if (ctx.profile.support_int8) {
+ WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8,
+ sizeof(u8), &StorageDefinitions::U8);
+ } else {
+ WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset8(offset), ctx.Const(8u));
+ }
}
void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) {
- WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8,
- sizeof(s8), &StorageDefinitions::S8);
+ if (ctx.profile.support_int8) {
+ WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8,
+ sizeof(s8), &StorageDefinitions::S8);
+ } else {
+ WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset8(offset), ctx.Const(8u));
+ }
}
void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) {
- WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16,
- sizeof(u16), &StorageDefinitions::U16);
+ if (ctx.profile.support_int16) {
+ WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16,
+ sizeof(u16), &StorageDefinitions::U16);
+ } else {
+ WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset16(offset), ctx.Const(16u));
+ }
}
void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) {
- WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16,
- sizeof(s16), &StorageDefinitions::S16);
+ if (ctx.profile.support_int16) {
+ WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16,
+ sizeof(s16), &StorageDefinitions::S16);
+ } else {
+ WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset16(offset), ctx.Const(16u));
+ }
}
void EmitWriteStorage32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 0442adc83..a27f2f73a 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -480,6 +480,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineTextures(program.info, texture_binding, bindings.texture_scaling_index);
DefineImages(program.info, image_binding, bindings.image_scaling_index);
DefineAttributeMemAccess(program.info);
+ DefineWriteStorageCasLoopFunction(program.info);
DefineGlobalMemoryFunctions(program.info);
DefineRescalingInput(program.info);
DefineRenderArea(program.info);
@@ -877,6 +878,56 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
}
}
+void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) {
+ if (profile.support_int8 && profile.support_int16) {
+ return;
+ }
+ if (!info.uses_int8 && !info.uses_int16) {
+ return;
+ }
+
+ AddCapability(spv::Capability::VariablePointersStorageBuffer);
+
+ const Id ptr_type{TypePointer(spv::StorageClass::StorageBuffer, U32[1])};
+ const Id func_type{TypeFunction(void_id, ptr_type, U32[1], U32[1], U32[1])};
+ const Id func{OpFunction(void_id, spv::FunctionControlMask::MaskNone, func_type)};
+ const Id pointer{OpFunctionParameter(ptr_type)};
+ const Id value{OpFunctionParameter(U32[1])};
+ const Id bit_offset{OpFunctionParameter(U32[1])};
+ const Id bit_count{OpFunctionParameter(U32[1])};
+
+ AddLabel();
+ const Id scope_device{Const(1u)};
+ const Id ordering_relaxed{u32_zero_value};
+ const Id body_label{OpLabel()};
+ const Id continue_label{OpLabel()};
+ const Id endloop_label{OpLabel()};
+ const Id beginloop_label{OpLabel()};
+ OpBranch(beginloop_label);
+
+ AddLabel(beginloop_label);
+ OpLoopMerge(endloop_label, continue_label, spv::LoopControlMask::MaskNone);
+ OpBranch(body_label);
+
+ AddLabel(body_label);
+ const Id expected_value{OpLoad(U32[1], pointer)};
+ const Id desired_value{OpBitFieldInsert(U32[1], expected_value, value, bit_offset, bit_count)};
+ const Id actual_value{OpAtomicCompareExchange(U32[1], pointer, scope_device, ordering_relaxed,
+ ordering_relaxed, desired_value, expected_value)};
+ const Id store_successful{OpIEqual(U1, expected_value, actual_value)};
+ OpBranchConditional(store_successful, endloop_label, continue_label);
+
+ AddLabel(endloop_label);
+ OpReturn();
+
+ AddLabel(continue_label);
+ OpBranch(beginloop_label);
+
+ OpFunctionEnd();
+
+ write_storage_cas_loop_func = func;
+}
+
void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
if (!info.uses_global_memory || !profile.support_int64) {
return;
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index 56019ad89..40adcb6b6 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -325,6 +325,8 @@ public:
Id f32x2_min_cas{};
Id f32x2_max_cas{};
+ Id write_storage_cas_loop_func{};
+
Id load_global_func_u32{};
Id load_global_func_u32x2{};
Id load_global_func_u32x4{};
@@ -372,6 +374,7 @@ private:
void DefineTextures(const Info& info, u32& binding, u32& scaling_index);
void DefineImages(const Info& info, u32& binding, u32& scaling_index);
void DefineAttributeMemAccess(const Info& info);
+ void DefineWriteStorageCasLoopFunction(const Info& info);
void DefineGlobalMemoryFunctions(const Info& info);
void DefineRescalingInput(const Info& info);
void DefineRescalingInputPushConstant();
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 8b340ee6c..48ce860ad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -41,7 +41,7 @@ void UpdateController(Core::HID::EmulatedController* controller,
bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type,
Core::Frontend::ControllerParameters parameters) {
switch (controller_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
return parameters.allow_pro_controller;
case Core::HID::NpadStyleIndex::JoyconDual:
return parameters.allow_dual_joycons;
@@ -462,7 +462,7 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
};
if (npad_style_set.fullkey == 1) {
- add_item(Core::HID::NpadStyleIndex::ProController, tr("Pro Controller"));
+ add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
}
if (npad_style_set.joycon_dual == 1) {
@@ -519,7 +519,7 @@ Core::HID::NpadStyleIndex QtControllerSelectorDialog::GetControllerTypeFromIndex
[index](const auto& pair) { return pair.first == index; });
if (it == pairs.end()) {
- return Core::HID::NpadStyleIndex::ProController;
+ return Core::HID::NpadStyleIndex::Fullkey;
}
return it->second;
@@ -549,7 +549,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
const QString stylesheet = [this, player_index] {
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
player_index)) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::GameCube:
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
case Core::HID::NpadStyleIndex::JoyconDual:
diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp
index bbe17c35e..ac81ace9e 100644
--- a/src/yuzu/applets/qt_software_keyboard.cpp
+++ b/src/yuzu/applets/qt_software_keyboard.cpp
@@ -832,7 +832,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
}();
switch (controller_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::GameCube:
ui->icon_controller->setStyleSheet(
QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index f3552191a..5dac9f1e7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1094,7 +1094,7 @@ void ConfigureInputPlayer::SetConnectableControllers() {
};
if (npad_style_set.fullkey == 1) {
- add_item(Core::HID::NpadStyleIndex::ProController, tr("Pro Controller"));
+ add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
}
if (npad_style_set.joycon_dual == 1) {
@@ -1149,7 +1149,7 @@ Core::HID::NpadStyleIndex ConfigureInputPlayer::GetControllerTypeFromIndex(int i
[index](const auto& pair) { return pair.first == index; });
if (it == index_controller_type_pairs.end()) {
- return Core::HID::NpadStyleIndex::ProController;
+ return Core::HID::NpadStyleIndex::Fullkey;
}
return it->second;
@@ -1178,7 +1178,7 @@ void ConfigureInputPlayer::UpdateInputDevices() {
void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
if (debug) {
- layout = Core::HID::NpadStyleIndex::ProController;
+ layout = Core::HID::NpadStyleIndex::Fullkey;
}
// List of all the widgets that will be hidden by any of the following layouts that need
@@ -1206,7 +1206,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
std::vector<QWidget*> layout_hidden;
switch (layout) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::Handheld:
layout_hidden = {
ui->buttonShoulderButtonsSLSRLeft,
@@ -1254,7 +1254,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
if (debug) {
- layout = Core::HID::NpadStyleIndex::ProController;
+ layout = Core::HID::NpadStyleIndex::Fullkey;
}
// List of all the widgets that will be disabled by any of the following layouts that need
@@ -1271,7 +1271,7 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
std::vector<QWidget*> layout_disable;
switch (layout) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconLeft:
@@ -1304,7 +1304,7 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
// Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconLeft:
case Core::HID::NpadStyleIndex::Handheld:
// Show "Motion 1" and hide "Motion 2".
@@ -1333,11 +1333,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
void ConfigureInputPlayer::UpdateControllerButtonNames() {
auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
if (debug) {
- layout = Core::HID::NpadStyleIndex::ProController;
+ layout = Core::HID::NpadStyleIndex::Fullkey;
}
switch (layout) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 19fdca7d3..8f91f5e92 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -244,7 +244,7 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {
case Core::HID::NpadStyleIndex::GameCube:
DrawGCController(p, center);
break;
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
default:
DrawProController(p, center);
break;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4f4c75f5c..33756febf 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2292,14 +2292,14 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
ASSERT(user_id);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
- *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
+ {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData, program_id, user_id->AsU128(), 0);
path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
} else {
// Device save data
const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath(
- *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
+ {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData, program_id, {}, 0);
path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path);
@@ -2662,8 +2662,8 @@ void GMainWindow::RemoveCacheStorage(u64 program_id) {
vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath(
- *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
- FileSys::SaveDataType::CacheStorage, 0 /* program_id */, {}, 0);
+ {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::CacheStorage,
+ 0 /* program_id */, {}, 0);
const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
@@ -3988,7 +3988,7 @@ void GMainWindow::OnToggleDockedMode() {
tr("Handheld controller can't be used on docked mode. Pro "
"controller will be selected."));
handheld->Disconnect();
- player_1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+ player_1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
player_1->Connect();
controller_dialog->refreshConfiguration();
}
diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp
index 2690b075d..0dbfca243 100644
--- a/src/yuzu/util/controller_navigation.cpp
+++ b/src/yuzu/util/controller_navigation.cpp
@@ -66,7 +66,7 @@ void ControllerNavigation::ControllerUpdateButton() {
}
switch (controller_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::GameCube:
@@ -116,7 +116,7 @@ void ControllerNavigation::ControllerUpdateStick() {
}
switch (controller_type) {
- case Core::HID::NpadStyleIndex::ProController:
+ case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::GameCube: