Browse Source

Merge remote-tracking branch 'origin/master' into typos3

Viktor Szépe 2 years ago
parent
commit
90ab89a0b0
100 changed files with 2173 additions and 1288 deletions
  1. 18 21
      .ci/scripts/format/script.sh
  2. 1 1
      .ci/scripts/linux/exec.sh
  3. 0 10
      .ci/yuzu-mainline-step2.yml
  4. 5 3
      .github/workflows/verify.yml
  5. 1 1
      LICENSES/BSD-2-Clause.txt
  6. 1 1
      LICENSES/BSD-3-Clause.txt
  7. 1 1
      LICENSES/MPL-2.0.txt
  8. 1 1
      externals/ffmpeg/CMakeLists.txt
  9. 1 1
      externals/nx_tzdb/tzdb_to_nx
  10. 26 57
      src/android/app/build.gradle.kts
  11. 33 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
  12. 98 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
  13. 105 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
  14. 8 28
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
  15. 36 52
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
  16. 22 35
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
  17. 28 73
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
  18. 7 29
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
  19. 77 115
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
  20. 9 19
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
  21. 27 45
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
  22. 13 23
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
  23. 17 33
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
  24. 16 29
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
  25. 2 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
  26. 52 24
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
  27. 27 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
  28. 49 80
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
  29. 9 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
  30. 3 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
  31. 0 3
      src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
  32. 18 0
      src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
  33. 5 5
      src/android/app/src/main/jni/native.cpp
  34. 2 2
      src/android/app/src/main/res/layout-w600dp/fragment_about.xml
  35. 2 2
      src/android/app/src/main/res/layout/fragment_about.xml
  36. 11 0
      src/android/app/src/main/res/menu/menu_driver_manager.xml
  37. 2 2
      src/android/app/src/main/res/values/arrays.xml
  38. 11 3
      src/audio_core/device/device_session.cpp
  39. 8 4
      src/audio_core/device/device_session.h
  40. 1 1
      src/audio_core/in/audio_in_system.cpp
  41. 7 6
      src/audio_core/in/audio_in_system.h
  42. 2 2
      src/audio_core/out/audio_out_system.cpp
  43. 7 6
      src/audio_core/out/audio_out_system.h
  44. 10 24
      src/common/page_table.cpp
  45. 4 0
      src/core/CMakeLists.txt
  46. 55 28
      src/core/arm/nce/patcher.cpp
  47. 17 10
      src/core/arm/nce/patcher.h
  48. 1 0
      src/core/core.cpp
  49. 29 10
      src/core/debugger/debugger.cpp
  50. 35 31
      src/core/debugger/gdbstub.cpp
  51. 14 1
      src/core/debugger/gdbstub.h
  52. 9 8
      src/core/file_sys/savedata_factory.cpp
  53. 7 3
      src/core/file_sys/savedata_factory.h
  54. 1 1
      src/core/frontend/applets/controller.cpp
  55. 2 2
      src/core/hle/kernel/k_memory_block_manager.cpp
  56. 2 2
      src/core/hle/kernel/k_memory_block_manager.h
  57. 31 3
      src/core/hle/kernel/k_page_table_base.cpp
  58. 1 0
      src/core/hle/kernel/k_page_table_base.h
  59. 8 2
      src/core/hle/kernel/k_process.cpp
  60. 28 3
      src/core/hle/kernel/kernel.cpp
  61. 4 2
      src/core/hle/kernel/kernel.h
  62. 5 3
      src/core/hle/kernel/svc/svc_process.cpp
  63. 16 3
      src/core/hle/service/acc/profile_manager.cpp
  64. 1 0
      src/core/hle/service/acc/profile_manager.h
  65. 4 3
      src/core/hle/service/am/am.cpp
  66. 29 7
      src/core/hle/service/audio/audin_u.cpp
  67. 20 6
      src/core/hle/service/audio/audout_u.cpp
  68. 50 177
      src/core/hle/service/filesystem/filesystem.cpp
  69. 29 30
      src/core/hle/service/filesystem/filesystem.h
  70. 31 24
      src/core/hle/service/filesystem/fsp_srv.cpp
  71. 6 0
      src/core/hle/service/filesystem/fsp_srv.h
  72. 37 0
      src/core/hle/service/filesystem/romfs_controller.cpp
  73. 31 0
      src/core/hle/service/filesystem/romfs_controller.h
  74. 99 0
      src/core/hle/service/filesystem/save_data_controller.cpp
  75. 35 0
      src/core/hle/service/filesystem/save_data_controller.h
  76. 4 3
      src/core/hle/service/glue/arp.cpp
  77. 4 6
      src/core/hle/service/hid/hid.cpp
  78. 1 1
      src/core/hle/service/hid/hid_server.cpp
  79. 2 1
      src/core/hle/service/nvnflinger/nvnflinger.cpp
  80. 37 48
      src/core/hle/service/pm/pm.cpp
  81. 7 2
      src/core/hle/service/server_manager.cpp
  82. 1 12
      src/core/hle/service/vi/display/vi_display.cpp
  83. 1 11
      src/core/hle/service/vi/display/vi_display.h
  84. 33 26
      src/core/hle/service/vi/vi.cpp
  85. 1 1
      src/core/hle/service/vi/vi.h
  86. 57 32
      src/core/loader/deconstructed_rom_directory.cpp
  87. 4 2
      src/core/loader/nca.cpp
  88. 6 4
      src/core/loader/nro.cpp
  89. 21 20
      src/core/loader/nso.cpp
  90. 2 1
      src/core/loader/nso.h
  91. 2 1
      src/core/loader/nsp.cpp
  92. 2 1
      src/core/loader/xci.cpp
  93. 34 0
      src/hid_core/CMakeLists.txt
  94. 11 11
      src/hid_core/frontend/emulated_controller.cpp
  95. 2 1
      src/hid_core/hid_types.h
  96. 1 1
      src/hid_core/hid_util.h
  97. 197 0
      src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
  98. 49 0
      src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
  99. 199 0
      src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
  100. 75 0
      src/hid_core/resources/abstracted_pad/abstract_button_handler.h

+ 18 - 21
.ci/scripts/format/script.sh

@@ -3,38 +3,35 @@
 # SPDX-FileCopyrightText: 2019 yuzu Emulator Project
 # SPDX-FileCopyrightText: 2019 yuzu Emulator Project
 # SPDX-License-Identifier: GPL-2.0-or-later
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 
-if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
-                 dist/*.svg dist/*.xml; then
+shopt -s nullglob globstar
+
+if git grep -nrI '\s$' src **/*.yml **/*.txt **/*.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop dist/*.svg dist/*.xml; then
     echo Trailing whitespace found, aborting
     echo Trailing whitespace found, aborting
     exit 1
     exit 1
 fi
 fi
 
 
 # Default clang-format points to default 3.5 version one
 # Default clang-format points to default 3.5 version one
-CLANG_FORMAT=${CLANG_FORMAT:-clang-format-15}
-$CLANG_FORMAT --version
-
-if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
-    # Get list of every file modified in this pull request
-    files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
-else
-    # Check everything for branch pushes
-    files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
-fi
+CLANG_FORMAT="${CLANG_FORMAT:-clang-format-15}"
+"$CLANG_FORMAT" --version
 
 
 # Turn off tracing for this because it's too verbose
 # Turn off tracing for this because it's too verbose
 set +x
 set +x
 
 
-for f in $files_to_lint; do
-    d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
-    if ! [ -z "$d" ]; then
-        echo "!!! $f not compliant to coding style, here is the fix:"
-        echo "$d"
-        fail=1
-    fi
+# Check everything for branch pushes
+FILES_TO_LINT="$(find src/ -name '*.cpp' -or -name '*.h')"
+
+for f in $FILES_TO_LINT; do
+    echo "$f"
+    "$CLANG_FORMAT" -i "$f"
 done
 done
 
 
-set -x
+DIFF=$(git -c core.fileMode=false diff)
 
 
-if [ "$fail" = 1 ]; then
+if [ ! -z "$DIFF" ]; then
+    echo "!!! Not compliant to coding style, here is the fix:"
+    echo "$DIFF"
     exit 1
     exit 1
 fi
 fi
+
+cd src/android
+./gradlew ktlintCheck

+ 1 - 1
.ci/scripts/linux/exec.sh

@@ -9,7 +9,7 @@ chmod a+x ./.ci/scripts/linux/docker.sh
 sudo chown -R 1027 ./
 sudo chown -R 1027 ./
 
 
 # The environment variables listed below:
 # The environment variables listed below:
-# AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION 
+# AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION
 #  are requested in src/common/CMakeLists.txt and appear to be provided somewhere in Azure DevOps
 #  are requested in src/common/CMakeLists.txt and appear to be provided somewhere in Azure DevOps
 
 
 docker run -e AZURECIREPO -e TITLEBARFORMATIDLE -e TITLEBARFORMATRUNNING -e DISPLAYVERSION -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1"
 docker run -e AZURECIREPO -e TITLEBARFORMATIDLE -e TITLEBARFORMATRUNNING -e DISPLAYVERSION -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1"

+ 0 - 10
.ci/yuzu-mainline-step2.yml

@@ -8,17 +8,7 @@ variables:
   DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
   DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
 
 
 stages:
 stages:
-- stage: format
-  displayName: 'format'
-  jobs:
-  - job: format
-    displayName: 'clang'
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - template: ./templates/format-check.yml
 - stage: build
 - stage: build
-  dependsOn: format
   displayName: 'build'
   displayName: 'build'
   jobs:
   jobs:
   - job: build
   - job: build

+ 5 - 3
.github/workflows/verify.yml

@@ -13,13 +13,15 @@ jobs:
   format:
   format:
     name: 'verify format'
     name: 'verify format'
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
-    container:
-      image: yuzuemu/build-environments:linux-clang-format
-      options: -u 1001
     steps:
     steps:
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
         with:
         with:
           submodules: false
           submodules: false
+      - name: set up JDK 17
+        uses: actions/setup-java@v3
+        with:
+          java-version: '17'
+          distribution: 'temurin'
       - name: 'Verify Formatting'
       - name: 'Verify Formatting'
         run: bash -ex ./.ci/scripts/format/script.sh
         run: bash -ex ./.ci/scripts/format/script.sh
   build:
   build:

+ 1 - 1
LICENSES/BSD-2-Clause.txt

@@ -1,4 +1,4 @@
-Copyright (c) <year> <owner> 
+Copyright (c) <year> <owner>
 
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
 

+ 1 - 1
LICENSES/BSD-3-Clause.txt

@@ -1,4 +1,4 @@
-Copyright (c) <year> <owner>. 
+Copyright (c) <year> <owner>.
 
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
 

+ 1 - 1
LICENSES/MPL-2.0.txt

@@ -35,7 +35,7 @@ Mozilla Public License Version 2.0
     means any form of the work other than Source Code Form.
     means any form of the work other than Source Code Form.
 
 
 1.7. "Larger Work"
 1.7. "Larger Work"
-    means a work that combines Covered Software with other material, in 
+    means a work that combines Covered Software with other material, in
     a separate file or files, that is not Covered Software.
     a separate file or files, that is not Covered Software.
 
 
 1.8. "License"
 1.8. "License"

+ 1 - 1
externals/ffmpeg/CMakeLists.txt

@@ -138,7 +138,7 @@ if (NOT WIN32 AND NOT ANDROID)
             --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
             --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
             --sysroot=${SYSROOT}
             --sysroot=${SYSROOT}
             --target-os=android
             --target-os=android
-            --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" 
+            --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
             --extra-ldflags="-nostdlib"
             --extra-ldflags="-nostdlib"
         )
         )
     endif()
     endif()

+ 1 - 1
externals/nx_tzdb/tzdb_to_nx

@@ -1 +1 @@
-Subproject commit f6680093bca30265c161581fd813d4ddd33f1e3e
+Subproject commit 404d39004570a26c734a9d1fa29ab4d63089c599

+ 26 - 57
src/android/app/build.gradle.kts

@@ -188,8 +188,15 @@ tasks.create<Delete>("ktlintReset") {
     delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
     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("loadKtlintReporters").dependsOn("ktlintReset")
-tasks.getByPath("preBuild").dependsOn("ktlintCheck")
 
 
 ktlint {
 ktlint {
     version.set("0.47.1")
     version.set("0.47.1")
@@ -228,71 +235,33 @@ dependencies {
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
     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)
             .directory(project.rootDir)
             .redirectOutput(ProcessBuilder.Redirect.PIPE)
             .redirectOutput(ProcessBuilder.Redirect.PIPE)
             .redirectError(ProcessBuilder.Redirect.PIPE)
             .redirectError(ProcessBuilder.Redirect.PIPE)
             .start().inputStream.bufferedReader().use { it.readText() }
             .start().inputStream.bufferedReader().use { it.readText() }
             .trim()
             .trim()
-            .replace(Regex("(-0)?-[^-]+$"), "")
     } catch (e: Exception) {
     } 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" }

+ 33 - 0
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
+        }
+    }
+}

+ 98 - 0
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()
+    }
+}

+ 105 - 0
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 }
+}

+ 8 - 28
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.LayoutInflater
 import android.view.ViewGroup
 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.databinding.ListItemAddonBinding
 import org.yuzu.yuzu_emu.model.Addon
 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 {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
         ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
         ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
             .also { return AddonViewHolder(it) }
             .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) :
     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.root.setOnClickListener {
                 binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
                 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 ->
             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
         }
         }
     }
     }
 }
 }

+ 36 - 52
src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt

@@ -4,13 +4,11 @@
 package org.yuzu.yuzu_emu.adapters
 package org.yuzu.yuzu_emu.adapters
 
 
 import android.view.LayoutInflater
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup
 import android.widget.Toast
 import android.widget.Toast
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.FragmentActivity
 import androidx.fragment.app.FragmentActivity
 import androidx.navigation.findNavController
 import androidx.navigation.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 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.Applet
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
 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(
     override fun onCreateViewHolder(
         parent: ViewGroup,
         parent: ViewGroup,
         viewType: Int
         viewType: Int
     ): AppletAdapter.AppletViewHolder {
     ): AppletAdapter.AppletViewHolder {
         CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
         CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@AppletAdapter) }
             .also { return AppletViewHolder(it) }
             .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) :
     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(
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
                     binding.icon.context.resources,
-                    applet.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                     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)
         }
         }
     }
     }
 }
 }

+ 22 - 35
src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt

@@ -4,12 +4,10 @@
 package org.yuzu.yuzu_emu.adapters
 package org.yuzu.yuzu_emu.adapters
 
 
 import android.view.LayoutInflater
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
 import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 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.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 
 class CabinetLauncherDialogAdapter(val fragment: Fragment) :
 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 {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
         DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
         DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
             .also { return CabinetModeViewHolder(it) }
             .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) :
     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(
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
                     binding.icon.context.resources,
-                    cabinetMode.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                     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)
         }
         }
     }
     }
 }
 }

+ 28 - 73
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.LayoutInflater
 import android.view.View
 import android.view.View
 import android.view.ViewGroup
 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.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.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) :
 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 {
     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) :
     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 {
             binding.apply {
-                radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
+                radioButton.isChecked = model.selected
                 root.setOnClickListener {
                 root.setOnClickListener {
-                    onSelectDriver(bindingAdapterPosition)
+                    selectItem(bindingAdapterPosition) {
+                        driverViewModel.onDriverSelected(it)
+                        driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+                    }
                 }
                 }
                 buttonDelete.setOnClickListener {
                 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
                 // Delay marquee by 3s
@@ -80,38 +54,19 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
                     },
                     },
                     3000
                     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
                     version.visibility = View.VISIBLE
                     description.visibility = View.VISIBLE
                     description.visibility = View.VISIBLE
                     buttonDelete.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
-        }
-    }
 }
 }

+ 7 - 29
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.LayoutInflater
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.fragment.app.FragmentActivity
 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.databinding.CardFolderBinding
 import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
 import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
 import org.yuzu.yuzu_emu.model.GameDir
 import org.yuzu.yuzu_emu.model.GameDir
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 
 class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
 class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
-    ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
-        AsyncDifferConfig.Builder(DiffCallback()).build()
-    ) {
+    AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() {
     override fun onCreateViewHolder(
     override fun onCreateViewHolder(
         parent: ViewGroup,
         parent: ViewGroup,
         viewType: Int
         viewType: Int
@@ -29,18 +24,11 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
             .also { return FolderViewHolder(it) }
             .also { return FolderViewHolder(it) }
     }
     }
 
 
-    override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
-        holder.bind(currentList[position])
-
     inner class FolderViewHolder(val binding: CardFolderBinding) :
     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 {
             binding.apply {
-                path.text = Uri.parse(gameDir.uriString).path
+                path.text = Uri.parse(model.uriString).path
                 path.postDelayed(
                 path.postDelayed(
                     {
                     {
                         path.isSelected = true
                         path.isSelected = true
@@ -50,7 +38,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
                 )
                 )
 
 
                 buttonEdit.setOnClickListener {
                 buttonEdit.setOnClickListener {
-                    GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
+                    GameFolderPropertiesDialogFragment.newInstance(model)
                         .show(
                         .show(
                             activity.supportFragmentManager,
                             activity.supportFragmentManager,
                             GameFolderPropertiesDialogFragment.TAG
                             GameFolderPropertiesDialogFragment.TAG
@@ -58,19 +46,9 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
                 }
                 }
 
 
                 buttonDelete.setOnClickListener {
                 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
-        }
-    }
 }
 }

+ 77 - 115
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.net.Uri
 import android.text.TextUtils
 import android.text.TextUtils
 import android.view.LayoutInflater
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.ImageView
 import android.widget.Toast
 import android.widget.Toast
@@ -25,10 +24,6 @@ import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import androidx.navigation.findNavController
 import androidx.navigation.findNavController
 import androidx.preference.PreferenceManager
 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.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 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.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 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.databinding.CardGameBinding
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.utils.GameIconUtils
 import org.yuzu.yuzu_emu.utils.GameIconUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 
 class GameAdapter(private val activity: AppCompatActivity) :
 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 {
     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) :
     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
             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(
             binding.textGameTitle.postDelayed(
                 {
                 {
@@ -160,16 +59,79 @@ class GameAdapter(private val activity: AppCompatActivity) :
                 },
                 },
                 3000
                 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
         }
         }
     }
     }
 }
 }

+ 9 - 19
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.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
 import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
 import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.model.GameProperty
 import org.yuzu.yuzu_emu.model.GameProperty
 import org.yuzu.yuzu_emu.model.InstallableProperty
 import org.yuzu.yuzu_emu.model.InstallableProperty
 import org.yuzu.yuzu_emu.model.SubmenuProperty
 import org.yuzu.yuzu_emu.model.SubmenuProperty
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 
 class GamePropertiesAdapter(
 class GamePropertiesAdapter(
     private val viewLifecycle: LifecycleOwner,
     private val viewLifecycle: LifecycleOwner,
     private var properties: List<GameProperty>
     private var properties: List<GameProperty>
-) :
-    RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
+) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) {
     override fun onCreateViewHolder(
     override fun onCreateViewHolder(
         parent: ViewGroup,
         parent: ViewGroup,
         viewType: Int
         viewType: Int
-    ): GamePropertyViewHolder {
+    ): AbstractViewHolder<GameProperty> {
         val inflater = LayoutInflater.from(parent.context)
         val inflater = LayoutInflater.from(parent.context)
         return when (viewType) {
         return when (viewType) {
             PropertyType.Submenu.ordinal -> {
             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 {
     override fun getItemViewType(position: Int): Int {
         return when (properties[position]) {
         return when (properties[position]) {
             is SubmenuProperty -> PropertyType.Submenu.ordinal
             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) :
     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 {
             binding.root.setOnClickListener {
                 submenuProperty.action.invoke()
                 submenuProperty.action.invoke()
@@ -108,9 +98,9 @@ class GamePropertiesAdapter(
     }
     }
 
 
     inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
     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.title.setText(installableProperty.titleId)
             binding.description.setText(installableProperty.descriptionId)
             binding.description.setText(installableProperty.descriptionId)

+ 27 - 45
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.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
 import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.HomeSetting
 import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 
 class HomeSettingAdapter(
 class HomeSettingAdapter(
     private val activity: AppCompatActivity,
     private val activity: AppCompatActivity,
     private val viewLifecycle: LifecycleOwner,
     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 {
     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) :
     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(
             binding.optionIcon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
                     activity.resources,
-                    option.iconId,
+                    model.iconId,
                     activity.theme
                     activity.theme
                 )
                 )
             )
             )
 
 
-            when (option.titleId) {
+            when (model.titleId) {
                 R.string.get_early_access ->
                 R.string.get_early_access ->
                     binding.optionLayout.background =
                     binding.optionLayout.background =
                         ContextCompat.getDrawable(
                         ContextCompat.getDrawable(
@@ -85,7 +53,7 @@ class HomeSettingAdapter(
                         )
                         )
             }
             }
 
 
-            if (!option.isEnabled.invoke()) {
+            if (!model.isEnabled.invoke()) {
                 binding.optionTitle.alpha = 0.5f
                 binding.optionTitle.alpha = 0.5f
                 binding.optionDescription.alpha = 0.5f
                 binding.optionDescription.alpha = 0.5f
                 binding.optionIcon.alpha = 0.5f
                 binding.optionIcon.alpha = 0.5f
@@ -93,7 +61,7 @@ class HomeSettingAdapter(
 
 
             viewLifecycle.lifecycleScope.launch {
             viewLifecycle.lifecycleScope.launch {
                 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
                 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    option.details.collect { updateOptionDetails(it) }
+                    model.details.collect { updateOptionDetails(it) }
                 }
                 }
             }
             }
             binding.optionDetail.postDelayed(
             binding.optionDetail.postDelayed(
@@ -103,6 +71,20 @@ class HomeSettingAdapter(
                 },
                 },
                 3000
                 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) {
         private fun updateOptionDetails(detailString: String) {

+ 13 - 23
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.LayoutInflater
 import android.view.View
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
 import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
 import org.yuzu.yuzu_emu.model.Installable
 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(
     override fun onCreateViewHolder(
         parent: ViewGroup,
         parent: ViewGroup,
         viewType: Int
         viewType: Int
     ): InstallableAdapter.InstallableViewHolder {
     ): 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) :
     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.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.visibility = View.VISIBLE
-                binding.buttonExport.setOnClickListener { installable.export.invoke() }
+                binding.buttonExport.setOnClickListener { model.export.invoke() }
             }
             }
         }
         }
     }
     }

+ 17 - 33
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.View
 import android.view.ViewGroup
 import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
 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.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
 import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
 import org.yuzu.yuzu_emu.model.License
 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 {
     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)
         }
         }
     }
     }
 }
 }

+ 16 - 29
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.appcompat.app.AppCompatActivity
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.content.res.ResourcesCompat
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.button.MaterialButton
 import org.yuzu.yuzu_emu.databinding.PageSetupBinding
 import org.yuzu.yuzu_emu.databinding.PageSetupBinding
 import org.yuzu.yuzu_emu.model.HomeViewModel
 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.SetupPage
 import org.yuzu.yuzu_emu.model.StepState
 import org.yuzu.yuzu_emu.model.StepState
 import org.yuzu.yuzu_emu.utils.ViewUtils
 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 {
     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) :
     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.buttonAction.visibility = View.INVISIBLE
                 binding.textConfirmation.visibility = View.VISIBLE
                 binding.textConfirmation.visibility = View.VISIBLE
             }
             }
@@ -50,31 +37,31 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
             binding.icon.setImageDrawable(
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
                     activity.resources,
-                    page.iconId,
+                    model.iconId,
                     activity.theme
                     activity.theme
                 )
                 )
             )
             )
-            binding.textTitle.text = activity.resources.getString(page.titleId)
+            binding.textTitle.text = activity.resources.getString(model.titleId)
             binding.textDescription.text =
             binding.textDescription.text =
-                Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
+                Html.fromHtml(activity.resources.getString(model.descriptionId), 0)
 
 
             binding.buttonAction.apply {
             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(
                     icon = ResourcesCompat.getDrawable(
                         activity.resources,
                         activity.resources,
-                        page.buttonIconId,
+                        model.buttonIconId,
                         activity.theme
                         activity.theme
                     )
                     )
                 }
                 }
                 iconGravity =
                 iconGravity =
-                    if (page.leftAlignedIcon) {
+                    if (model.leftAlignedIcon) {
                         MaterialButton.ICON_GRAVITY_START
                         MaterialButton.ICON_GRAVITY_START
                     } else {
                     } else {
                         MaterialButton.ICON_GRAVITY_END
                         MaterialButton.ICON_GRAVITY_END
                     }
                     }
                 setOnClickListener {
                 setOnClickListener {
-                    page.buttonAction.invoke(this@SetupPageViewHolder)
+                    model.buttonAction.invoke(this@SetupPageViewHolder)
                 }
                 }
             }
             }
         }
         }

+ 2 - 2
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.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 =
             val clipBoard =
                 requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                 requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
             val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)
             val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)

+ 52 - 24
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt

@@ -3,6 +3,7 @@
 
 
 package org.yuzu.yuzu_emu.fragments
 package org.yuzu.yuzu_emu.fragments
 
 
+import android.annotation.SuppressLint
 import android.os.Bundle
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View
@@ -13,20 +14,26 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.GridLayoutManager
 import com.google.android.material.transition.MaterialSharedAxis
 import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.DriverAdapter
 import org.yuzu.yuzu_emu.adapters.DriverAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
 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.DriverViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.utils.FileUtil
 import org.yuzu.yuzu_emu.utils.FileUtil
 import org.yuzu.yuzu_emu.utils.GpuDriverHelper
 import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.NativeConfig
 import java.io.File
 import java.io.File
 import java.io.IOException
 import java.io.IOException
 
 
@@ -55,12 +62,43 @@ class DriverManagerFragment : Fragment() {
         return binding.root
         return binding.root
     }
     }
 
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         super.onViewCreated(view, savedInstanceState)
         homeViewModel.setNavigationVisibility(visible = false, animated = true)
         homeViewModel.setNavigationVisibility(visible = false, animated = true)
         homeViewModel.setStatusBarShadeVisibility(visible = false)
         homeViewModel.setStatusBarShadeVisibility(visible = false)
 
 
         driverViewModel.onOpenDriverManager(args.game)
         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) {
         if (!driverViewModel.isInteractionAllowed.value) {
             DriversLoadingDialogFragment().show(
             DriversLoadingDialogFragment().show(
@@ -85,25 +123,6 @@ class DriverManagerFragment : Fragment() {
             adapter = DriverAdapter(driverViewModel)
             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()
         setInsets()
     }
     }
 
 
@@ -160,7 +179,7 @@ class DriverManagerFragment : Fragment() {
                 false
                 false
             ) {
             ) {
                 val driverPath =
                 val driverPath =
-                    "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+                    "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
                 val driverFile = File(driverPath)
                 val driverFile = File(driverPath)
 
 
                 // Ignore file exceptions when a user selects an invalid zip
                 // Ignore file exceptions when a user selects an invalid zip
@@ -177,12 +196,21 @@ class DriverManagerFragment : Fragment() {
 
 
                 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
                 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
                 val driverInList =
                 val driverInList =
-                    driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+                    driverViewModel.driverData.firstOrNull { it.second == driverData }
                 if (driverInList != null) {
                 if (driverInList != null) {
                     return@newInstance getString(R.string.driver_already_installed)
                     return@newInstance getString(R.string.driver_already_installed)
                 } else {
                 } 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()
                 return@newInstance Any()
             }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
             }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)

+ 27 - 0
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 ?: ""
+            )
+    }
+}

+ 49 - 80
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.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 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.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 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.GpuDriverHelper
 import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
 import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
 import org.yuzu.yuzu_emu.utils.NativeConfig
 import org.yuzu.yuzu_emu.utils.NativeConfig
-import java.io.BufferedOutputStream
 import java.io.File
 import java.io.File
 
 
 class DriverViewModel : ViewModel() {
 class DriverViewModel : ViewModel() {
@@ -38,97 +38,81 @@ class DriverViewModel : ViewModel() {
             !loading && ready && !deleting
             !loading && ready && !deleting
         }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false)
         }.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
     // Used for showing which driver is currently installed within the driver manager card
     private val _selectedDriverTitle = MutableStateFlow("")
     private val _selectedDriverTitle = MutableStateFlow("")
     val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
     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 {
     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 {
         } 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?) {
     fun onCloseDriverManager(game: Game?) {
         _isDeletingDrivers.value = true
         _isDeletingDrivers.value = true
-        StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first)
         updateDriverNameForGame(game)
         updateDriverNameForGame(game)
         if (game == null) {
         if (game == null) {
             NativeConfig.saveGlobalConfig()
             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?) {
     fun updateDriverNameForGame(game: Game?) {
         if (!GpuDriverHelper.supportsCustomDriverLoading()) {
         if (!GpuDriverHelper.supportsCustomDriverLoading()) {
             return
             return
@@ -217,7 +187,6 @@ class DriverViewModel : ViewModel() {
 
 
     private fun setDriverReady() {
     private fun setDriverReady() {
         _isDriverReady.value = true
         _isDriverReady.value = true
-        _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
-            ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+        updateName()
     }
     }
 }
 }

+ 9 - 0
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)
+}

+ 3 - 0
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.IndeterminateProgressDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.AddonViewModel
 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.GamesViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.model.TaskState
 import org.yuzu.yuzu_emu.model.TaskState
@@ -58,6 +59,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
     private val gamesViewModel: GamesViewModel by viewModels()
     private val gamesViewModel: GamesViewModel by viewModels()
     private val taskViewModel: TaskViewModel by viewModels()
     private val taskViewModel: TaskViewModel by viewModels()
     private val addonViewModel: AddonViewModel by viewModels()
     private val addonViewModel: AddonViewModel by viewModels()
+    private val driverViewModel: DriverViewModel by viewModels()
 
 
     override var themeId: Int = 0
     override var themeId: Int = 0
 
 
@@ -689,6 +691,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 NativeLibrary.initializeSystem(true)
                 NativeLibrary.initializeSystem(true)
                 NativeConfig.initializeGlobalConfig()
                 NativeConfig.initializeGlobalConfig()
                 gamesViewModel.reloadGames(false)
                 gamesViewModel.reloadGames(false)
+                driverViewModel.reloadDriverData()
 
 
                 return@newInstance getString(R.string.user_data_import_success)
                 return@newInstance getString(R.string.user_data_import_success)
             }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
             }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)

+ 0 - 3
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 }
                 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
                 ?.distinct()
                 ?.distinct()
                 ?.toMutableList() ?: mutableListOf()
                 ?.toMutableList() ?: mutableListOf()
-
-        // TODO: Get system driver information
-        drivers.add(0, Pair("", GpuDriverMetadata()))
         return drivers
         return drivers
     }
     }
 
 

+ 18 - 0
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)
+}

+ 5 - 5
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);
         jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
 
 
         if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::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();
             handheld->Disconnect();
         }
         }
     }
     }
@@ -770,8 +770,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
     ASSERT(user_id);
     ASSERT(user_id);
 
 
     const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
     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);
     const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
     if (!Common::FS::CreateParentDirs(full_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);
                                                             FileSys::Mode::Read);
 
 
     const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
     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);
         program_id, user_id->AsU128(), 0);
     return ToJString(env, user_save_data_path);
     return ToJString(env, user_save_data_path);
 }
 }

+ 2 - 2
src/android/app/src/main/res/layout-w600dp/fragment_about.xml

@@ -147,7 +147,7 @@
                     android:layout_marginHorizontal="20dp" />
                     android:layout_marginHorizontal="20dp" />
 
 
                 <LinearLayout
                 <LinearLayout
-                    android:id="@+id/button_build_hash"
+                    android:id="@+id/button_version_name"
                     android:layout_width="match_parent"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_height="wrap_content"
                     android:background="?attr/selectableItemBackground"
                     android:background="?attr/selectableItemBackground"
@@ -164,7 +164,7 @@
                         android:textAlignment="viewStart" />
                         android:textAlignment="viewStart" />
 
 
                     <com.google.android.material.textview.MaterialTextView
                     <com.google.android.material.textview.MaterialTextView
-                        android:id="@+id/text_build_hash"
+                        android:id="@+id/text_version_name"
                         style="@style/TextAppearance.Material3.BodyMedium"
                         style="@style/TextAppearance.Material3.BodyMedium"
                         android:layout_width="match_parent"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_height="wrap_content"

+ 2 - 2
src/android/app/src/main/res/layout/fragment_about.xml

@@ -148,7 +148,7 @@
                 android:layout_marginHorizontal="20dp" />
                 android:layout_marginHorizontal="20dp" />
 
 
             <LinearLayout
             <LinearLayout
-                android:id="@+id/button_build_hash"
+                android:id="@+id/button_version_name"
                 android:layout_width="match_parent"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_height="wrap_content"
                 android:paddingVertical="16dp"
                 android:paddingVertical="16dp"
@@ -165,7 +165,7 @@
                     android:text="@string/build" />
                     android:text="@string/build" />
 
 
                 <com.google.android.material.textview.MaterialTextView
                 <com.google.android.material.textview.MaterialTextView
-                    android:id="@+id/text_build_hash"
+                    android:id="@+id/text_version_name"
                     style="@style/TextAppearance.Material3.BodyMedium"
                     style="@style/TextAppearance.Material3.BodyMedium"
                     android:layout_width="match_parent"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_height="wrap_content"

+ 11 - 0
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>

+ 2 - 2
src/android/app/src/main/res/values/arrays.xml

@@ -228,10 +228,10 @@
         <item>R</item>
         <item>R</item>
         <item>ZL</item>
         <item>ZL</item>
         <item>ZR</item>
         <item>ZR</item>
-        <item>@string/gamepad_left_stick</item>
-        <item>@string/gamepad_right_stick</item>
         <item>L3</item>
         <item>L3</item>
         <item>R3</item>
         <item>R3</item>
+        <item>@string/gamepad_left_stick</item>
+        <item>@string/gamepad_right_stick</item>
         <item>@string/gamepad_d_pad</item>
         <item>@string/gamepad_d_pad</item>
     </string-array>
     </string-array>
 
 

+ 11 - 3
src/audio_core/device/device_session.cpp

@@ -10,6 +10,8 @@
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 #include "core/memory.h"
 #include "core/memory.h"
 
 
+#include "core/hle/kernel/k_process.h"
+
 namespace AudioCore {
 namespace AudioCore {
 
 
 using namespace std::literals;
 using namespace std::literals;
@@ -25,7 +27,7 @@ DeviceSession::~DeviceSession() {
 }
 }
 
 
 Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
 Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
-                                 u16 channel_count_, size_t session_id_, u32 handle_,
+                                 u16 channel_count_, size_t session_id_, Kernel::KProcess* handle_,
                                  u64 applet_resource_user_id_, Sink::StreamType type_) {
                                  u64 applet_resource_user_id_, Sink::StreamType type_) {
     if (stream) {
     if (stream) {
         Finalize();
         Finalize();
@@ -36,6 +38,7 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for
     channel_count = channel_count_;
     channel_count = channel_count_;
     session_id = session_id_;
     session_id = session_id_;
     handle = handle_;
     handle = handle_;
+    handle->Open();
     applet_resource_user_id = applet_resource_user_id_;
     applet_resource_user_id = applet_resource_user_id_;
 
 
     if (type == Sink::StreamType::In) {
     if (type == Sink::StreamType::In) {
@@ -54,6 +57,11 @@ void DeviceSession::Finalize() {
         sink->CloseStream(stream);
         sink->CloseStream(stream);
         stream = nullptr;
         stream = nullptr;
     }
     }
+
+    if (handle) {
+        handle->Close();
+        handle = nullptr;
+    }
 }
 }
 
 
 void DeviceSession::Start() {
 void DeviceSession::Start() {
@@ -91,7 +99,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
             stream->AppendBuffer(new_buffer, tmp_samples);
             stream->AppendBuffer(new_buffer, tmp_samples);
         } else {
         } else {
             Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
             Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
-                system.ApplicationMemory(), buffer.samples, buffer.size / sizeof(s16));
+                handle->GetMemory(), buffer.samples, buffer.size / sizeof(s16));
             stream->AppendBuffer(new_buffer, samples);
             stream->AppendBuffer(new_buffer, samples);
         }
         }
     }
     }
@@ -100,7 +108,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
 void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
 void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
     if (type == Sink::StreamType::In) {
     if (type == Sink::StreamType::In) {
         auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
         auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
-        system.ApplicationMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
+        handle->GetMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
     }
     }
 }
 }
 
 

+ 8 - 4
src/audio_core/device/device_session.h

@@ -20,6 +20,10 @@ struct EventType;
 } // namespace Timing
 } // namespace Timing
 } // namespace Core
 } // namespace Core
 
 
+namespace Kernel {
+class KProcess;
+} // namespace Kernel
+
 namespace AudioCore {
 namespace AudioCore {
 
 
 namespace Sink {
 namespace Sink {
@@ -44,13 +48,13 @@ public:
      * @param sample_format           - Sample format for this device's output.
      * @param sample_format           - Sample format for this device's output.
      * @param channel_count           - Number of channels for this device (2 or 6).
      * @param channel_count           - Number of channels for this device (2 or 6).
      * @param session_id              - This session's id.
      * @param session_id              - This session's id.
-     * @param handle                  - Handle for this device session (unused).
+     * @param handle                  - Process handle for this device session.
      * @param applet_resource_user_id - Applet resource user id for this device session (unused).
      * @param applet_resource_user_id - Applet resource user id for this device session (unused).
      * @param type                    - Type of this stream (Render, In, Out).
      * @param type                    - Type of this stream (Render, In, Out).
      * @return Result code for this call.
      * @return Result code for this call.
      */
      */
     Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
     Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
-                      size_t session_id, u32 handle, u64 applet_resource_user_id,
+                      size_t session_id, Kernel::KProcess* handle, u64 applet_resource_user_id,
                       Sink::StreamType type);
                       Sink::StreamType type);
 
 
     /**
     /**
@@ -137,8 +141,8 @@ private:
     u16 channel_count{};
     u16 channel_count{};
     /// Session id of this device session
     /// Session id of this device session
     size_t session_id{};
     size_t session_id{};
-    /// Handle of this device session
-    u32 handle{};
+    /// Process handle of device memory owner
+    Kernel::KProcess* handle{};
     /// Applet resource user id of this device session
     /// Applet resource user id of this device session
     u64 applet_resource_user_id{};
     u64 applet_resource_user_id{};
     /// Total number of samples played by this device session
     /// Total number of samples played by this device session

+ 1 - 1
src/audio_core/in/audio_in_system.cpp

@@ -57,7 +57,7 @@ Result System::IsConfigValid(const std::string_view device_name,
 }
 }
 
 
 Result System::Initialize(std::string device_name, const AudioInParameter& in_params,
 Result System::Initialize(std::string device_name, const AudioInParameter& in_params,
-                          const u32 handle_, const u64 applet_resource_user_id_) {
+                          Kernel::KProcess* handle_, const u64 applet_resource_user_id_) {
     auto result{IsConfigValid(device_name, in_params)};
     auto result{IsConfigValid(device_name, in_params)};
     if (result.IsError()) {
     if (result.IsError()) {
         return result;
         return result;

+ 7 - 6
src/audio_core/in/audio_in_system.h

@@ -19,7 +19,8 @@ class System;
 
 
 namespace Kernel {
 namespace Kernel {
 class KEvent;
 class KEvent;
-}
+class KProcess;
+} // namespace Kernel
 
 
 namespace AudioCore::AudioIn {
 namespace AudioCore::AudioIn {
 
 
@@ -93,12 +94,12 @@ public:
      *
      *
      * @param device_name             - The name of the requested input device.
      * @param device_name             - The name of the requested input device.
      * @param in_params               - Input parameters, see AudioInParameter.
      * @param in_params               - Input parameters, see AudioInParameter.
-     * @param handle                  - Unused.
+     * @param handle                  - Process handle.
      * @param applet_resource_user_id - Unused.
      * @param applet_resource_user_id - Unused.
      * @return Result code.
      * @return Result code.
      */
      */
-    Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle,
-                      u64 applet_resource_user_id);
+    Result Initialize(std::string device_name, const AudioInParameter& in_params,
+                      Kernel::KProcess* handle, u64 applet_resource_user_id);
 
 
     /**
     /**
      * Start this system.
      * Start this system.
@@ -244,8 +245,8 @@ public:
 private:
 private:
     /// Core system
     /// Core system
     Core::System& system;
     Core::System& system;
-    /// (Unused)
-    u32 handle{};
+    /// Process handle
+    Kernel::KProcess* handle{};
     /// (Unused)
     /// (Unused)
     u64 applet_resource_user_id{};
     u64 applet_resource_user_id{};
     /// Buffer event, signalled when a buffer is ready
     /// Buffer event, signalled when a buffer is ready

+ 2 - 2
src/audio_core/out/audio_out_system.cpp

@@ -48,8 +48,8 @@ Result System::IsConfigValid(std::string_view device_name,
     return Service::Audio::ResultInvalidChannelCount;
     return Service::Audio::ResultInvalidChannelCount;
 }
 }
 
 
-Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_,
-                          u64 applet_resource_user_id_) {
+Result System::Initialize(std::string device_name, const AudioOutParameter& in_params,
+                          Kernel::KProcess* handle_, u64 applet_resource_user_id_) {
     auto result = IsConfigValid(device_name, in_params);
     auto result = IsConfigValid(device_name, in_params);
     if (result.IsError()) {
     if (result.IsError()) {
         return result;
         return result;

+ 7 - 6
src/audio_core/out/audio_out_system.h

@@ -19,7 +19,8 @@ class System;
 
 
 namespace Kernel {
 namespace Kernel {
 class KEvent;
 class KEvent;
-}
+class KProcess;
+} // namespace Kernel
 
 
 namespace AudioCore::AudioOut {
 namespace AudioCore::AudioOut {
 
 
@@ -84,12 +85,12 @@ public:
      *
      *
      * @param device_name             - The name of the requested output device.
      * @param device_name             - The name of the requested output device.
      * @param in_params               - Input parameters, see AudioOutParameter.
      * @param in_params               - Input parameters, see AudioOutParameter.
-     * @param handle                  - Unused.
+     * @param handle                  - Process handle.
      * @param applet_resource_user_id - Unused.
      * @param applet_resource_user_id - Unused.
      * @return Result code.
      * @return Result code.
      */
      */
-    Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle,
-                      u64 applet_resource_user_id);
+    Result Initialize(std::string device_name, const AudioOutParameter& in_params,
+                      Kernel::KProcess* handle, u64 applet_resource_user_id);
 
 
     /**
     /**
      * Start this system.
      * Start this system.
@@ -228,8 +229,8 @@ public:
 private:
 private:
     /// Core system
     /// Core system
     Core::System& system;
     Core::System& system;
-    /// (Unused)
-    u32 handle{};
+    /// Process handle
+    Kernel::KProcess* handle{};
     /// (Unused)
     /// (Unused)
     u64 applet_resource_user_id{};
     u64 applet_resource_user_id{};
     /// Buffer event, signalled when a buffer is ready
     /// Buffer event, signalled when a buffer is ready

+ 10 - 24
src/common/page_table.cpp

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 
 #include "common/page_table.h"
 #include "common/page_table.h"
+#include "common/scope_exit.h"
 
 
 namespace Common {
 namespace Common {
 
 
@@ -11,29 +12,10 @@ PageTable::~PageTable() noexcept = default;
 
 
 bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
 bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
                                Common::ProcessAddress address) const {
                                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 {
 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->phys_addr = 0;
     out_entry->block_size = page_size;
     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.
     // Validate that we can read the actual entry.
     const auto page = context->next_page;
     const auto page = context->next_page;
     if (page >= backing_addr.size()) {
     if (page >= backing_addr.size()) {
@@ -55,8 +43,6 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
 
 
     // Populate the results.
     // Populate the results.
     out_entry->phys_addr = phys_addr + context->next_offset;
     out_entry->phys_addr = phys_addr + context->next_offset;
-    context->next_page = page + 1;
-    context->next_offset += page_size;
 
 
     return true;
     return true;
 }
 }

+ 4 - 0
src/core/CMakeLists.txt

@@ -490,6 +490,10 @@ add_library(core STATIC
     hle/service/filesystem/fsp_pr.h
     hle/service/filesystem/fsp_pr.h
     hle/service/filesystem/fsp_srv.cpp
     hle/service/filesystem/fsp_srv.cpp
     hle/service/filesystem/fsp_srv.h
     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.cpp
     hle/service/fgm/fgm.h
     hle/service/fgm/fgm.h
     hle/service/friend/friend.cpp
     hle/service/friend/friend.cpp

+ 55 - 28
src/core/arm/nce/patcher.cpp

@@ -22,14 +22,10 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
 constexpr size_t MaxRelativeBranch = 128_MiB;
 constexpr size_t MaxRelativeBranch = 128_MiB;
 constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
 constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
 
 
-Patcher::Patcher() : c(m_patch_instructions) {}
-
-Patcher::~Patcher() = default;
-
-void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
-                        const Kernel::CodeSet::Segment& code) {
-    // Branch to the first instruction of the module.
-    this->BranchToModule(0);
+Patcher::Patcher() : c(m_patch_instructions) {
+    // The first word of the patch section is always a branch to the first instruction of the
+    // module.
+    c.dw(0);
 
 
     // Write save context helper function.
     // Write save context helper function.
     c.l(m_save_context);
     c.l(m_save_context);
@@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
     // Write load context helper function.
     // Write load context helper function.
     c.l(m_load_context);
     c.l(m_load_context);
     WriteLoadContext();
     WriteLoadContext();
+}
+
+Patcher::~Patcher() = default;
+
+bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
+                        const Kernel::CodeSet::Segment& code) {
+    // If we have patched modules but cannot reach the new module, then it needs its own patcher.
+    const size_t image_size = program_image.size();
+    if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
+        return false;
+    }
+
+    // Add a new module patch to our list
+    modules.emplace_back();
+    curr_patch = &modules.back();
+
+    // The first word of the patch section is always a branch to the first instruction of the
+    // module.
+    curr_patch->m_branch_to_module_relocations.push_back({0, 0});
 
 
     // Retrieve text segment data.
     // Retrieve text segment data.
     const auto text = std::span{program_image}.subspan(code.offset, code.size);
     const auto text = std::span{program_image}.subspan(code.offset, code.size);
@@ -94,16 +109,17 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
         }
         }
 
 
         if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
         if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
-            m_exclusives.push_back(i);
+            curr_patch->m_exclusives.push_back(i);
         }
         }
     }
     }
 
 
     // Determine patching mode for the final relocation step
     // Determine patching mode for the final relocation step
-    const size_t image_size = program_image.size();
+    total_program_size += image_size;
     this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
     this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
+    return true;
 }
 }
 
 
-void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
+bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
                               const Kernel::CodeSet::Segment& code,
                               const Kernel::CodeSet::Segment& code,
                               Kernel::PhysicalMemory& program_image,
                               Kernel::PhysicalMemory& program_image,
                               EntryTrampolines* out_trampolines) {
                               EntryTrampolines* out_trampolines) {
@@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         if (mode == PatchMode::PreText) {
         if (mode == PatchMode::PreText) {
             rc.B(rel.patch_offset - patch_size - rel.module_offset);
             rc.B(rel.patch_offset - patch_size - rel.module_offset);
         } else {
         } else {
-            rc.B(image_size - rel.module_offset + rel.patch_offset);
+            rc.B(total_program_size - rel.module_offset + rel.patch_offset);
         }
         }
     };
     };
 
 
@@ -129,7 +145,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         if (mode == PatchMode::PreText) {
         if (mode == PatchMode::PreText) {
             rc.B(patch_size - rel.patch_offset + rel.module_offset);
             rc.B(patch_size - rel.patch_offset + rel.module_offset);
         } else {
         } else {
-            rc.B(rel.module_offset - image_size - rel.patch_offset);
+            rc.B(rel.module_offset - total_program_size - rel.patch_offset);
         }
         }
     };
     };
 
 
@@ -137,7 +153,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         if (mode == PatchMode::PreText) {
         if (mode == PatchMode::PreText) {
             return GetInteger(load_base) + patch_offset;
             return GetInteger(load_base) + patch_offset;
         } else {
         } else {
-            return GetInteger(load_base) + image_size + patch_offset;
+            return GetInteger(load_base) + total_program_size + patch_offset;
         }
         }
     };
     };
 
 
@@ -150,39 +166,50 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
     };
     };
 
 
     // We are now ready to relocate!
     // We are now ready to relocate!
-    for (const Relocation& rel : m_branch_to_patch_relocations) {
+    auto& patch = modules[m_relocate_module_index++];
+    for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
         ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
         ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
     }
     }
-    for (const Relocation& rel : m_branch_to_module_relocations) {
+    for (const Relocation& rel : patch.m_branch_to_module_relocations) {
         ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
         ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
                                       rel);
                                       rel);
     }
     }
 
 
     // Rewrite PC constants and record post trampolines
     // Rewrite PC constants and record post trampolines
-    for (const Relocation& rel : m_write_module_pc_relocations) {
+    for (const Relocation& rel : patch.m_write_module_pc_relocations) {
         oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
         oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
         rc.dx(RebasePc(rel.module_offset));
         rc.dx(RebasePc(rel.module_offset));
     }
     }
-    for (const Trampoline& rel : m_trampolines) {
+    for (const Trampoline& rel : patch.m_trampolines) {
         out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
         out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
     }
     }
 
 
     // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
     // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
     // Convert to ordered to preserve this assumption.
     // Convert to ordered to preserve this assumption.
-    for (const ModuleTextAddress i : m_exclusives) {
+    for (const ModuleTextAddress i : patch.m_exclusives) {
         auto exclusive = Exclusive{text_words[i]};
         auto exclusive = Exclusive{text_words[i]};
         text_words[i] = exclusive.AsOrdered();
         text_words[i] = exclusive.AsOrdered();
     }
     }
 
 
-    // Copy to program image
-    if (this->mode == PatchMode::PreText) {
-        std::memcpy(program_image.data(), m_patch_instructions.data(),
-                    m_patch_instructions.size() * sizeof(u32));
-    } else {
-        program_image.resize(image_size + patch_size);
-        std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
-                    m_patch_instructions.size() * sizeof(u32));
+    // Remove the patched module size from the total. This is done so total_program_size
+    // always represents the distance from the currently patched module to the patch section.
+    total_program_size -= image_size;
+
+    // Only copy to the program image of the last module
+    if (m_relocate_module_index == modules.size()) {
+        if (this->mode == PatchMode::PreText) {
+            ASSERT(image_size == total_program_size);
+            std::memcpy(program_image.data(), m_patch_instructions.data(),
+                        m_patch_instructions.size() * sizeof(u32));
+        } else {
+            program_image.resize(image_size + patch_size);
+            std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
+                        m_patch_instructions.size() * sizeof(u32));
+        }
+        return true;
     }
     }
+
+    return false;
 }
 }
 
 
 size_t Patcher::GetSectionSize() const noexcept {
 size_t Patcher::GetSectionSize() const noexcept {
@@ -322,7 +349,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
 
 
     // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
     // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
     // state.
     // state.
-    m_trampolines.push_back({c.offset(), module_dest});
+    curr_patch->m_trampolines.push_back({c.offset(), module_dest});
 
 
     // Host called this location. Save the return address so we can
     // Host called this location. Save the return address so we can
     // unwind the stack properly when jumping back.
     // unwind the stack properly when jumping back.

+ 17 - 10
src/core/arm/nce/patcher.h

@@ -31,9 +31,9 @@ public:
     explicit Patcher();
     explicit Patcher();
     ~Patcher();
     ~Patcher();
 
 
-    void PatchText(const Kernel::PhysicalMemory& program_image,
+    bool PatchText(const Kernel::PhysicalMemory& program_image,
                    const Kernel::CodeSet::Segment& code);
                    const Kernel::CodeSet::Segment& code);
-    void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
+    bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
                          Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
                          Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
     size_t GetSectionSize() const noexcept;
     size_t GetSectionSize() const noexcept;
 
 
@@ -61,16 +61,16 @@ private:
 
 
 private:
 private:
     void BranchToPatch(uintptr_t module_dest) {
     void BranchToPatch(uintptr_t module_dest) {
-        m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
+        curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
     }
     }
 
 
     void BranchToModule(uintptr_t module_dest) {
     void BranchToModule(uintptr_t module_dest) {
-        m_branch_to_module_relocations.push_back({c.offset(), module_dest});
+        curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
         c.dw(0);
         c.dw(0);
     }
     }
 
 
     void WriteModulePc(uintptr_t module_dest) {
     void WriteModulePc(uintptr_t module_dest) {
-        m_write_module_pc_relocations.push_back({c.offset(), module_dest});
+        curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
         c.dx(0);
         c.dx(0);
     }
     }
 
 
@@ -84,15 +84,22 @@ private:
         uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
         uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
     };
     };
 
 
+    struct ModulePatch {
+        std::vector<Trampoline> m_trampolines;
+        std::vector<Relocation> m_branch_to_patch_relocations{};
+        std::vector<Relocation> m_branch_to_module_relocations{};
+        std::vector<Relocation> m_write_module_pc_relocations{};
+        std::vector<ModuleTextAddress> m_exclusives{};
+    };
+
     oaknut::VectorCodeGenerator c;
     oaknut::VectorCodeGenerator c;
-    std::vector<Trampoline> m_trampolines;
-    std::vector<Relocation> m_branch_to_patch_relocations{};
-    std::vector<Relocation> m_branch_to_module_relocations{};
-    std::vector<Relocation> m_write_module_pc_relocations{};
-    std::vector<ModuleTextAddress> m_exclusives{};
     oaknut::Label m_save_context{};
     oaknut::Label m_save_context{};
     oaknut::Label m_load_context{};
     oaknut::Label m_load_context{};
     PatchMode mode{PatchMode::None};
     PatchMode mode{PatchMode::None};
+    size_t total_program_size{};
+    size_t m_relocate_module_index{};
+    std::vector<ModulePatch> modules;
+    ModulePatch* curr_patch;
 };
 };
 
 
 } // namespace Core::NCE
 } // namespace Core::NCE

+ 1 - 0
src/core/core.cpp

@@ -413,6 +413,7 @@ struct System::Impl {
         kernel.ShutdownCores();
         kernel.ShutdownCores();
         services.reset();
         services.reset();
         service_manager.reset();
         service_manager.reset();
+        fs_controller.Reset();
         cheat_engine.reset();
         cheat_engine.reset();
         telemetry_session.reset();
         telemetry_session.reset();
         time_manager.Shutdown();
         time_manager.Shutdown();

+ 29 - 10
src/core/debugger/debugger.cpp

@@ -114,7 +114,7 @@ public:
     }
     }
 
 
     Kernel::KThread* GetActiveThread() override {
     Kernel::KThread* GetActiveThread() override {
-        return state->active_thread;
+        return state->active_thread.GetPointerUnsafe();
     }
     }
 
 
 private:
 private:
@@ -147,11 +147,14 @@ private:
 
 
         std::scoped_lock lk{connection_lock};
         std::scoped_lock lk{connection_lock};
 
 
+        // Find the process we are going to debug.
+        SetDebugProcess();
+
         // Ensure everything is stopped.
         // Ensure everything is stopped.
         PauseEmulation();
         PauseEmulation();
 
 
         // Set up the new frontend.
         // 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.
         // Set the new state. This will tear down any existing state.
         state = ConnectionState{
         state = ConnectionState{
@@ -194,15 +197,20 @@ private:
             UpdateActiveThread();
             UpdateActiveThread();
 
 
             if (state->info.type == SignalType::Watchpoint) {
             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 {
             } else {
-                frontend->Stopped(state->active_thread);
+                frontend->Stopped(std::addressof(*state->active_thread));
             }
             }
 
 
             break;
             break;
         case SignalType::ShuttingDown:
         case SignalType::ShuttingDown:
             frontend->ShuttingDown();
             frontend->ShuttingDown();
 
 
+            // Release members.
+            state->active_thread.Reset(nullptr);
+            debug_process.Reset(nullptr);
+
             // Wait for emulation to shut down gracefully now.
             // Wait for emulation to shut down gracefully now.
             state->signal_pipe.close();
             state->signal_pipe.close();
             state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
             state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
@@ -222,7 +230,7 @@ private:
                 stopped = true;
                 stopped = true;
                 PauseEmulation();
                 PauseEmulation();
                 UpdateActiveThread();
                 UpdateActiveThread();
-                frontend->Stopped(state->active_thread);
+                frontend->Stopped(state->active_thread.GetPointerUnsafe());
                 break;
                 break;
             }
             }
             case DebuggerAction::Continue:
             case DebuggerAction::Continue:
@@ -232,7 +240,7 @@ private:
                 MarkResumed([&] {
                 MarkResumed([&] {
                     state->active_thread->SetStepState(Kernel::StepState::StepPending);
                     state->active_thread->SetStepState(Kernel::StepState::StepPending);
                     state->active_thread->Resume(Kernel::SuspendType::Debug);
                     state->active_thread->Resume(Kernel::SuspendType::Debug);
-                    ResumeEmulation(state->active_thread);
+                    ResumeEmulation(state->active_thread.GetPointerUnsafe());
                 });
                 });
                 break;
                 break;
             case DebuggerAction::StepThreadLocked: {
             case DebuggerAction::StepThreadLocked: {
@@ -255,6 +263,7 @@ private:
     }
     }
 
 
     void PauseEmulation() {
     void PauseEmulation() {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
         Kernel::KScopedSchedulerLock sl{system.Kernel()};
         Kernel::KScopedSchedulerLock sl{system.Kernel()};
 
 
         // Put all threads to sleep on next scheduler round.
         // Put all threads to sleep on next scheduler round.
@@ -264,6 +273,9 @@ private:
     }
     }
 
 
     void ResumeEmulation(Kernel::KThread* except = nullptr) {
     void ResumeEmulation(Kernel::KThread* except = nullptr) {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+        Kernel::KScopedSchedulerLock sl{system.Kernel()};
+
         // Wake up all threads.
         // Wake up all threads.
         for (auto& thread : ThreadList()) {
         for (auto& thread : ThreadList()) {
             if (std::addressof(thread) == except) {
             if (std::addressof(thread) == except) {
@@ -277,15 +289,16 @@ private:
 
 
     template <typename Callback>
     template <typename Callback>
     void MarkResumed(Callback&& cb) {
     void MarkResumed(Callback&& cb) {
-        Kernel::KScopedSchedulerLock sl{system.Kernel()};
         stopped = false;
         stopped = false;
         cb();
         cb();
     }
     }
 
 
     void UpdateActiveThread() {
     void UpdateActiveThread() {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+
         auto& threads{ThreadList()};
         auto& threads{ThreadList()};
         for (auto& thread : threads) {
         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.
                 // Thread is still alive, no need to update.
                 return;
                 return;
             }
             }
@@ -293,12 +306,18 @@ private:
         state->active_thread = std::addressof(threads.front());
         state->active_thread = std::addressof(threads.front());
     }
     }
 
 
+private:
+    void SetDebugProcess() {
+        debug_process = std::move(system.Kernel().GetProcessList().back());
+    }
+
     Kernel::KProcess::ThreadList& ThreadList() {
     Kernel::KProcess::ThreadList& ThreadList() {
-        return system.ApplicationProcess()->GetThreadList();
+        return debug_process->GetThreadList();
     }
     }
 
 
 private:
 private:
     System& system;
     System& system;
+    Kernel::KScopedAutoObject<Kernel::KProcess> debug_process;
     std::unique_ptr<DebuggerFrontend> frontend;
     std::unique_ptr<DebuggerFrontend> frontend;
 
 
     boost::asio::io_context io_context;
     boost::asio::io_context io_context;
@@ -310,7 +329,7 @@ private:
         boost::process::async_pipe signal_pipe;
         boost::process::async_pipe signal_pipe;
 
 
         SignalInfo info;
         SignalInfo info;
-        Kernel::KThread* active_thread;
+        Kernel::KScopedAutoObject<Kernel::KThread> active_thread;
         std::array<u8, 4096> client_data;
         std::array<u8, 4096> client_data;
         bool pipe_data;
         bool pipe_data;
     };
     };

+ 35 - 31
src/core/debugger/gdbstub.cpp

@@ -108,9 +108,9 @@ static std::string EscapeXML(std::string_view data) {
     return escaped;
     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>();
         arch = std::make_unique<GDBStubA64>();
     } else {
     } else {
         arch = std::make_unique<GDBStubA32>();
         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))};
         const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
 
 
         std::vector<u8> mem(size);
         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.
             // Restore any bytes belonging to replaced instructions.
             auto it = replaced_instructions.lower_bound(addr);
             auto it = replaced_instructions.lower_bound(addr);
             for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
             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_substr{std::string_view(command).substr(mem_sep)};
         const auto mem{Common::HexStringToVector(mem_substr, false)};
         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);
             SendReply(GDB_STUB_REPLY_OK);
         } else {
         } else {
             SendReply(GDB_STUB_REPLY_ERR);
             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 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))};
     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);
         SendReply(GDB_STUB_REPLY_ERR);
         return;
         return;
     }
     }
@@ -362,22 +362,20 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
 
 
     switch (type) {
     switch (type) {
     case BreakpointType::Software:
     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;
         success = true;
         break;
         break;
     case BreakpointType::WriteWatch:
     case BreakpointType::WriteWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Write);
+        success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
         break;
         break;
     case BreakpointType::ReadWatch:
     case BreakpointType::ReadWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Read);
+        success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
         break;
         break;
     case BreakpointType::AccessWatch:
     case BreakpointType::AccessWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(
-            addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+        success =
+            GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
         break;
         break;
     case BreakpointType::Hardware:
     case BreakpointType::Hardware:
     default:
     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 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))};
     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);
         SendReply(GDB_STUB_REPLY_ERR);
         return;
         return;
     }
     }
@@ -411,24 +409,22 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
     case BreakpointType::Software: {
     case BreakpointType::Software: {
         const auto orig_insn{replaced_instructions.find(addr)};
         const auto orig_insn{replaced_instructions.find(addr)};
         if (orig_insn != replaced_instructions.end()) {
         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);
             replaced_instructions.erase(addr);
             success = true;
             success = true;
         }
         }
         break;
         break;
     }
     }
     case BreakpointType::WriteWatch:
     case BreakpointType::WriteWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Write);
+        success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
         break;
         break;
     case BreakpointType::ReadWatch:
     case BreakpointType::ReadWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Read);
+        success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
         break;
         break;
     case BreakpointType::AccessWatch:
     case BreakpointType::AccessWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(
-            addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+        success =
+            GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
         break;
         break;
     case BreakpointType::Hardware:
     case BreakpointType::Hardware:
     default:
     default:
@@ -466,10 +462,10 @@ void GDBStub::HandleQuery(std::string_view command) {
         const auto target_xml{arch->GetTargetXML()};
         const auto target_xml{arch->GetTargetXML()};
         SendReply(PaginateBuffer(target_xml, command.substr(30)));
         SendReply(PaginateBuffer(target_xml, command.substr(30)));
     } else if (command.starts_with("Offsets")) {
     } 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)));
         SendReply(fmt::format("TextSeg={:x}", GetInteger(main_offset)));
     } else if (command.starts_with("Xfer:libraries:read::")) {
     } else if (command.starts_with("Xfer:libraries:read::")) {
-        auto modules = Core::FindModules(system.ApplicationProcess());
+        auto modules = Core::FindModules(GetProcess());
 
 
         std::string buffer;
         std::string buffer;
         buffer += R"(<?xml version="1.0"?>)";
         buffer += R"(<?xml version="1.0"?>)";
@@ -483,7 +479,7 @@ void GDBStub::HandleQuery(std::string_view command) {
         SendReply(PaginateBuffer(buffer, command.substr(21)));
         SendReply(PaginateBuffer(buffer, command.substr(21)));
     } else if (command.starts_with("fThreadInfo")) {
     } else if (command.starts_with("fThreadInfo")) {
         // beginning of list
         // beginning of list
-        const auto& threads = system.ApplicationProcess()->GetThreadList();
+        const auto& threads = GetProcess()->GetThreadList();
         std::vector<std::string> thread_ids;
         std::vector<std::string> thread_ids;
         for (const auto& thread : threads) {
         for (const auto& thread : threads) {
             thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
             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 += R"(<?xml version="1.0"?>)";
         buffer += "<threads>";
         buffer += "<threads>";
 
 
-        const auto& threads = system.ApplicationProcess()->GetThreadList();
+        const auto& threads = GetProcess()->GetThreadList();
         for (const auto& thread : threads) {
         for (const auto& thread : threads) {
             auto thread_name{Core::GetThreadName(&thread)};
             auto thread_name{Core::GetThreadName(&thread)};
             if (!thread_name) {
             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_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
     std::string reply;
     std::string reply;
 
 
-    auto* process = system.ApplicationProcess();
+    auto* process = GetProcess();
     auto& page_table = process->GetPageTable();
     auto& page_table = process->GetPageTable();
 
 
     const char* commands = "Commands:\n"
     const char* commands = "Commands:\n"
@@ -714,7 +710,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
 }
 }
 
 
 Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
 Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
-    auto& threads{system.ApplicationProcess()->GetThreadList()};
+    auto& threads{GetProcess()->GetThreadList()};
     for (auto& thread : threads) {
     for (auto& thread : threads) {
         if (thread.GetThreadId() == thread_id) {
         if (thread.GetThreadId() == thread_id) {
             return std::addressof(thread);
             return std::addressof(thread);
@@ -783,4 +779,12 @@ void GDBStub::SendStatus(char status) {
     backend.WriteToClient(buf);
     backend.WriteToClient(buf);
 }
 }
 
 
+Kernel::KProcess* GDBStub::GetProcess() {
+    return debug_process;
+}
+
+Core::Memory::Memory& GDBStub::GetMemory() {
+    return GetProcess()->GetMemory();
+}
+
 } // namespace Core
 } // namespace Core

+ 14 - 1
src/core/debugger/gdbstub.h

@@ -12,13 +12,22 @@
 #include "core/debugger/debugger_interface.h"
 #include "core/debugger/debugger_interface.h"
 #include "core/debugger/gdbstub_arch.h"
 #include "core/debugger/gdbstub_arch.h"
 
 
+namespace Kernel {
+class KProcess;
+}
+
+namespace Core::Memory {
+class Memory;
+}
+
 namespace Core {
 namespace Core {
 
 
 class System;
 class System;
 
 
 class GDBStub : public DebuggerFrontend {
 class GDBStub : public DebuggerFrontend {
 public:
 public:
-    explicit GDBStub(DebuggerBackend& backend, Core::System& system);
+    explicit GDBStub(DebuggerBackend& backend, Core::System& system,
+                     Kernel::KProcess* debug_process);
     ~GDBStub() override;
     ~GDBStub() override;
 
 
     void Connected() override;
     void Connected() override;
@@ -42,8 +51,12 @@ private:
     void SendReply(std::string_view data);
     void SendReply(std::string_view data);
     void SendStatus(char status);
     void SendStatus(char status);
 
 
+    Kernel::KProcess* GetProcess();
+    Core::Memory::Memory& GetMemory();
+
 private:
 private:
     Core::System& system;
     Core::System& system;
+    Kernel::KProcess* debug_process;
     std::unique_ptr<GDBStubArch> arch;
     std::unique_ptr<GDBStubArch> arch;
     std::vector<char> current_command;
     std::vector<char> current_command;
     std::map<VAddr, u32> replaced_instructions;
     std::map<VAddr, u32> replaced_instructions;

+ 9 - 8
src/core/file_sys/savedata_factory.cpp

@@ -97,8 +97,9 @@ std::string SaveDataAttribute::DebugInfo() const {
                        static_cast<u8>(rank), index);
                        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
     // Delete all temporary storages
     // On hardware, it is expected that temporary storage be empty at first use.
     // On hardware, it is expected that temporary storage be empty at first use.
     dir->DeleteSubdirectoryRecursive("temp");
     dir->DeleteSubdirectoryRecursive("temp");
@@ -110,7 +111,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
     PrintSaveDataAttributeWarnings(meta);
     PrintSaveDataAttributeWarnings(meta);
 
 
     const auto save_directory =
     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);
     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 {
 VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
 
 
     const auto save_directory =
     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);
     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,
                                          SaveDataSpaceId space, SaveDataType type, u64 title_id,
                                          u128 user_id, u64 save_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
     // 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.
     // be interpreted as the title id of the current process.
     if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
     if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
         if (title_id == 0) {
         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,
 SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
                                                u128 user_id) const {
                                                u128 user_id) const {
     const auto path =
     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 relative_dir = GetOrCreateDirectoryRelative(dir, path);
 
 
     const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
     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,
 void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
                                         SaveDataSize new_value) const {
                                         SaveDataSize new_value) const {
     const auto path =
     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 relative_dir = GetOrCreateDirectoryRelative(dir, path);
 
 
     const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());
     const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());

+ 7 - 3
src/core/file_sys/savedata_factory.h

@@ -87,10 +87,13 @@ constexpr const char* GetSaveDataSizeFileName() {
     return ".yuzu_save_size";
     return ".yuzu_save_size";
 }
 }
 
 
+using ProgramId = u64;
+
 /// File system interface to the SaveData archive
 /// File system interface to the SaveData archive
 class SaveDataFactory {
 class SaveDataFactory {
 public:
 public:
-    explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
+    explicit SaveDataFactory(Core::System& system_, ProgramId program_id_,
+                             VirtualDir save_directory_);
     ~SaveDataFactory();
     ~SaveDataFactory();
 
 
     VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
     VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
@@ -99,7 +102,7 @@ public:
     VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
     VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
 
 
     static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
     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);
                                    SaveDataType type, u64 title_id, u128 user_id, u64 save_id);
     static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
     static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
 
 
@@ -110,8 +113,9 @@ public:
     void SetAutoCreate(bool state);
     void SetAutoCreate(bool state);
 
 
 private:
 private:
-    VirtualDir dir;
     Core::System& system;
     Core::System& system;
+    ProgramId program_id;
+    VirtualDir dir;
     bool auto_create{true};
     bool auto_create{true};
 };
 };
 
 

+ 1 - 1
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:
         // Connect controllers based on the following priority list from highest to lowest priority:
         // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
         // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
         if (parameters.allow_pro_controller) {
         if (parameters.allow_pro_controller) {
-            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
             controller->Connect(true);
             controller->Connect(true);
         } else if (parameters.allow_dual_joycons) {
         } else if (parameters.allow_dual_joycons) {
             controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
             controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);

+ 2 - 2
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,
 void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager,
-                                   HostUnmapCallback&& host_unmap_callback) {
+                                   BlockCallback&& block_callback) {
     // Erase every block until we have none left.
     // Erase every block until we have none left.
     auto it = m_memory_block_tree.begin();
     auto it = m_memory_block_tree.begin();
     while (it != m_memory_block_tree.end()) {
     while (it != m_memory_block_tree.end()) {
         KMemoryBlock* block = std::addressof(*it);
         KMemoryBlock* block = std::addressof(*it);
         it = m_memory_block_tree.erase(it);
         it = m_memory_block_tree.erase(it);
+        block_callback(block->GetAddress(), block->GetSize());
         slab_manager->Free(block);
         slab_manager->Free(block);
-        host_unmap_callback(block->GetAddress(), block->GetSize());
     }
     }
 
 
     ASSERT(m_memory_block_tree.empty());
     ASSERT(m_memory_block_tree.empty());

+ 2 - 2
src/core/hle/kernel/k_memory_block_manager.h

@@ -85,11 +85,11 @@ public:
 public:
 public:
     KMemoryBlockManager();
     KMemoryBlockManager();
 
 
-    using HostUnmapCallback = std::function<void(Common::ProcessAddress, u64)>;
+    using BlockCallback = std::function<void(Common::ProcessAddress, u64)>;
 
 
     Result Initialize(KProcessAddress st, KProcessAddress nd,
     Result Initialize(KProcessAddress st, KProcessAddress nd,
                       KMemoryBlockSlabManager* slab_manager);
                       KMemoryBlockSlabManager* slab_manager);
-    void Finalize(KMemoryBlockSlabManager* slab_manager, HostUnmapCallback&& host_unmap_callback);
+    void Finalize(KMemoryBlockSlabManager* slab_manager, BlockCallback&& block_callback);
 
 
     iterator end() {
     iterator end() {
         return m_memory_block_tree.end();
         return m_memory_block_tree.end();

+ 31 - 3
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));
                                                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() {
 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);
             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.
     // 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.
     // Free any unsafe mapped memory.
     if (m_mapped_unsafe_physical_memory) {
     if (m_mapped_unsafe_physical_memory) {

+ 1 - 0
src/core/hle/kernel/k_page_table_base.h

@@ -241,6 +241,7 @@ public:
                                 KResourceLimit* resource_limit, Core::Memory::Memory& memory,
                                 KResourceLimit* resource_limit, Core::Memory::Memory& memory,
                                 KProcessAddress aslr_space_start);
                                 KProcessAddress aslr_space_start);
 
 
+    Result FinalizeProcess();
     void Finalize();
     void Finalize();
 
 
     bool IsKernel() const {
     bool IsKernel() const {

+ 8 - 2
src/core/hle/kernel/k_process.cpp

@@ -171,6 +171,12 @@ void KProcess::Finalize() {
         m_resource_limit->Close();
         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.
     // Perform inherited finalization.
     KSynchronizationObject::Finalize();
     KSynchronizationObject::Finalize();
 }
 }
@@ -1233,10 +1239,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
     ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
     ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
 
 
 #ifdef HAS_NCE
 #ifdef HAS_NCE
-    if (this->IsApplication() && Settings::IsNceEnabled()) {
+    const auto& patch = code_set.PatchSegment();
+    if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
         auto& buffer = m_kernel.System().DeviceMemory().buffer;
         auto& buffer = m_kernel.System().DeviceMemory().buffer;
         const auto& code = code_set.CodeSegment();
         const auto& code = code_set.CodeSegment();
-        const auto& patch = code_set.PatchSegment();
         buffer.Protect(GetInteger(base_addr + code.addr), code.size,
         buffer.Protect(GetInteger(base_addr + code.addr), code.size,
                        Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
                        Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
         buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
         buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,

+ 28 - 3
src/core/hle/kernel/kernel.cpp

@@ -112,7 +112,14 @@ struct KernelCore::Impl {
             old_process->Close();
             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_object_id = 0;
         next_kernel_process_id = KProcess::InitialProcessIdMin;
         next_kernel_process_id = KProcess::InitialProcessIdMin;
@@ -770,6 +777,7 @@ struct KernelCore::Impl {
     std::atomic<u64> next_thread_id{1};
     std::atomic<u64> next_thread_id{1};
 
 
     // Lists all processes that exist in the current session.
     // Lists all processes that exist in the current session.
+    std::mutex process_list_lock;
     std::vector<KProcess*> process_list;
     std::vector<KProcess*> process_list;
     std::atomic<KProcess*> application_process{};
     std::atomic<KProcess*> application_process{};
     std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
     std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
@@ -869,9 +877,19 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() {
 }
 }
 
 
 void KernelCore::AppendNewProcess(KProcess* process) {
 void KernelCore::AppendNewProcess(KProcess* process) {
+    process->Open();
+
+    std::scoped_lock lk{impl->process_list_lock};
     impl->process_list.push_back(process);
     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) {
 void KernelCore::MakeApplicationProcess(KProcess* process) {
     impl->MakeApplicationProcess(process);
     impl->MakeApplicationProcess(process);
 }
 }
@@ -884,8 +902,15 @@ const KProcess* KernelCore::ApplicationProcess() const {
     return impl->application_process;
     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() {
 Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {

+ 4 - 2
src/core/hle/kernel/kernel.h

@@ -5,6 +5,7 @@
 
 
 #include <array>
 #include <array>
 #include <functional>
 #include <functional>
+#include <list>
 #include <memory>
 #include <memory>
 #include <string>
 #include <string>
 #include <unordered_map>
 #include <unordered_map>
@@ -116,8 +117,9 @@ public:
     /// Retrieves a shared pointer to the system resource limit instance.
     /// Retrieves a shared pointer to the system resource limit instance.
     KResourceLimit* GetSystemResourceLimit();
     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 AppendNewProcess(KProcess* process);
+    void RemoveProcess(KProcess* process);
 
 
     /// Makes the given process the new application process.
     /// Makes the given process the new application process.
     void MakeApplicationProcess(KProcess* process);
     void MakeApplicationProcess(KProcess* process);
@@ -129,7 +131,7 @@ public:
     const KProcess* ApplicationProcess() const;
     const KProcess* ApplicationProcess() const;
 
 
     /// Retrieves the list of processes.
     /// Retrieves the list of processes.
-    const std::vector<KProcess*>& GetProcessList() const;
+    std::list<KScopedAutoObject<KProcess>> GetProcessList();
 
 
     /// Gets the sole instance of the global scheduler
     /// Gets the sole instance of the global scheduler
     Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
     Kernel::GlobalSchedulerContext& GlobalSchedulerContext();

+ 5 - 3
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);
     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 num_processes = process_list.size();
     const auto copy_amount =
     const auto copy_amount =
         std::min(static_cast<std::size_t>(out_process_ids_size), num_processes);
         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);
         out_process_ids += sizeof(u64);
     }
     }
 
 

+ 16 - 3
src/core/hle/service/acc/profile_manager.cpp

@@ -61,9 +61,7 @@ ProfileManager::ProfileManager() {
     OpenUser(*GetUser(current));
     OpenUser(*GetUser(current));
 }
 }
 
 
-ProfileManager::~ProfileManager() {
-    WriteUserSaveFile();
-}
+ProfileManager::~ProfileManager() = default;
 
 
 /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
 /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
 /// internal management of the users profiles
 /// internal management of the users profiles
@@ -113,6 +111,8 @@ Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username)
         return ERROR_USER_ALREADY_EXISTS;
         return ERROR_USER_ALREADY_EXISTS;
     }
     }
 
 
+    is_save_needed = true;
+
     return AddUser({
     return AddUser({
         .user_uuid = uuid,
         .user_uuid = uuid,
         .username = username,
         .username = username,
@@ -326,6 +326,9 @@ bool ProfileManager::RemoveUser(UUID uuid) {
     profiles[*index] = ProfileInfo{};
     profiles[*index] = ProfileInfo{};
     std::stable_partition(profiles.begin(), profiles.end(),
     std::stable_partition(profiles.begin(), profiles.end(),
                           [](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
                           [](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
+
+    is_save_needed = true;
+
     return true;
     return true;
 }
 }
 
 
@@ -340,6 +343,8 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
     profile.username = profile_new.username;
     profile.username = profile_new.username;
     profile.creation_time = profile_new.timestamp;
     profile.creation_time = profile_new.timestamp;
 
 
+    is_save_needed = true;
+
     return true;
     return true;
 }
 }
 
 
@@ -348,6 +353,7 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
     const auto index = GetUserIndex(uuid);
     const auto index = GetUserIndex(uuid);
     if (index.has_value() && SetProfileBase(uuid, profile_new)) {
     if (index.has_value() && SetProfileBase(uuid, profile_new)) {
         profiles[*index].data = data_new;
         profiles[*index].data = data_new;
+        is_save_needed = true;
         return true;
         return true;
     }
     }
 
 
@@ -391,6 +397,10 @@ void ProfileManager::ParseUserSaveFile() {
 }
 }
 
 
 void ProfileManager::WriteUserSaveFile() {
 void ProfileManager::WriteUserSaveFile() {
+    if (!is_save_needed) {
+        return;
+    }
+
     ProfileDataRaw raw{};
     ProfileDataRaw raw{};
 
 
     for (std::size_t i = 0; i < MAX_USERS; ++i) {
     for (std::size_t i = 0; i < MAX_USERS; ++i) {
@@ -423,7 +433,10 @@ void ProfileManager::WriteUserSaveFile() {
     if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) {
     if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) {
         LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
         LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
                                  "made in current session will be saved.");
                                  "made in current session will be saved.");
+        return;
     }
     }
+
+    is_save_needed = false;
 }
 }
 
 
 }; // namespace Service::Account
 }; // namespace Service::Account

+ 1 - 0
src/core/hle/service/acc/profile_manager.h

@@ -103,6 +103,7 @@ private:
     std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
     std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
     bool RemoveProfileAtIndex(std::size_t index);
     bool RemoveProfileAtIndex(std::size_t index);
 
 
+    bool is_save_needed{};
     std::array<ProfileInfo, MAX_USERS> profiles{};
     std::array<ProfileInfo, MAX_USERS> profiles{};
     std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
     std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
     std::size_t user_count{};
     std::size_t user_count{};

+ 4 - 3
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_su.h"
 #include "core/hle/service/caps/caps_types.h"
 #include "core/hle/service/caps/caps_types.h"
 #include "core/hle/service/filesystem/filesystem.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/ipc_helpers.h"
 #include "core/hle/service/ns/ns.h"
 #include "core/hle/service/ns/ns.h"
 #include "core/hle/service/nvnflinger/fb_share_buffer_manager.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;
     attribute.type = FileSys::SaveDataType::SaveData;
 
 
     FileSys::VirtualDir save_data{};
     FileSys::VirtualDir save_data{};
-    const auto res = system.GetFileSystemController().CreateSaveData(
+    const auto res = system.GetFileSystemController().OpenSaveDataController()->CreateSaveData(
         &save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
         &save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
 
 
     IPC::ResponseBuilder rb{ctx, 4};
     IPC::ResponseBuilder rb{ctx, 4};
@@ -2353,7 +2354,7 @@ void IApplicationFunctions::ExtendSaveData(HLERequestContext& ctx) {
               "new_journal={:016X}",
               "new_journal={:016X}",
               static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
               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,
         type, system.GetApplicationProcessProgramID(), user_id,
         {new_normal_size, new_journal_size});
         {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],
     LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1],
               user_id[0]);
               user_id[0]);
 
 
-    const auto size = system.GetFileSystemController().ReadSaveDataSize(
+    const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
         type, system.GetApplicationProcessProgramID(), user_id);
         type, system.GetApplicationProcessProgramID(), user_id);
 
 
     IPC::ResponseBuilder rb{ctx, 6};
     IPC::ResponseBuilder rb{ctx, 6};

+ 29 - 7
src/core/hle/service/audio/audin_u.cpp

@@ -18,11 +18,11 @@ using namespace AudioCore::AudioIn;
 class IAudioIn final : public ServiceFramework<IAudioIn> {
 class IAudioIn final : public ServiceFramework<IAudioIn> {
 public:
 public:
     explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
     explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
-                      const std::string& device_name, const AudioInParameter& in_params, u32 handle,
-                      u64 applet_resource_user_id)
+                      const std::string& device_name, const AudioInParameter& in_params,
+                      Kernel::KProcess* handle, u64 applet_resource_user_id)
         : ServiceFramework{system_, "IAudioIn"},
         : ServiceFramework{system_, "IAudioIn"},
           service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
           service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
-          impl{std::make_shared<In>(system_, manager, event, session_id)} {
+          process{handle}, impl{std::make_shared<In>(system_, manager, event, session_id)} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
             {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
@@ -45,6 +45,8 @@ public:
 
 
         RegisterHandlers(functions);
         RegisterHandlers(functions);
 
 
+        process->Open();
+
         if (impl->GetSystem()
         if (impl->GetSystem()
                 .Initialize(device_name, in_params, handle, applet_resource_user_id)
                 .Initialize(device_name, in_params, handle, applet_resource_user_id)
                 .IsError()) {
                 .IsError()) {
@@ -55,6 +57,7 @@ public:
     ~IAudioIn() override {
     ~IAudioIn() override {
         impl->Free();
         impl->Free();
         service_context.CloseEvent(event);
         service_context.CloseEvent(event);
+        process->Close();
     }
     }
 
 
     [[nodiscard]] std::shared_ptr<In> GetImpl() {
     [[nodiscard]] std::shared_ptr<In> GetImpl() {
@@ -196,6 +199,7 @@ private:
 
 
     KernelHelpers::ServiceContext service_context;
     KernelHelpers::ServiceContext service_context;
     Kernel::KEvent* event;
     Kernel::KEvent* event;
+    Kernel::KProcess* process;
     std::shared_ptr<AudioCore::AudioIn::In> impl;
     std::shared_ptr<AudioCore::AudioIn::In> impl;
     Common::ScratchBuffer<u64> released_buffer;
     Common::ScratchBuffer<u64> released_buffer;
 };
 };
@@ -267,6 +271,14 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto handle{ctx.GetCopyHandle(0)};
     auto handle{ctx.GetCopyHandle(0)};
 
 
+    auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
+    if (process.IsNull()) {
+        LOG_ERROR(Service_Audio, "Failed to get process handle");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
     std::scoped_lock l{impl->mutex};
     std::scoped_lock l{impl->mutex};
     auto link{impl->LinkToManager()};
     auto link{impl->LinkToManager()};
     if (link.IsError()) {
     if (link.IsError()) {
@@ -287,8 +299,9 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
     LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
     LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
               impl->num_free_sessions);
               impl->num_free_sessions);
 
 
-    auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
-                                               in_params, handle, applet_resource_user_id);
+    auto audio_in =
+        std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, in_params,
+                                   process.GetPointerUnsafe(), applet_resource_user_id);
     impl->sessions[new_session_id] = audio_in->GetImpl();
     impl->sessions[new_session_id] = audio_in->GetImpl();
     impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
     impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
 
 
@@ -318,6 +331,14 @@ void AudInU::OpenAudioInProtocolSpecified(HLERequestContext& ctx) {
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto handle{ctx.GetCopyHandle(0)};
     auto handle{ctx.GetCopyHandle(0)};
 
 
+    auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
+    if (process.IsNull()) {
+        LOG_ERROR(Service_Audio, "Failed to get process handle");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
     std::scoped_lock l{impl->mutex};
     std::scoped_lock l{impl->mutex};
     auto link{impl->LinkToManager()};
     auto link{impl->LinkToManager()};
     if (link.IsError()) {
     if (link.IsError()) {
@@ -338,8 +359,9 @@ void AudInU::OpenAudioInProtocolSpecified(HLERequestContext& ctx) {
     LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
     LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
               impl->num_free_sessions);
               impl->num_free_sessions);
 
 
-    auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
-                                               in_params, handle, applet_resource_user_id);
+    auto audio_in =
+        std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, in_params,
+                                   process.GetPointerUnsafe(), applet_resource_user_id);
     impl->sessions[new_session_id] = audio_in->GetImpl();
     impl->sessions[new_session_id] = audio_in->GetImpl();
     impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
     impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
 
 

+ 20 - 6
src/core/hle/service/audio/audout_u.cpp

@@ -26,9 +26,10 @@ class IAudioOut final : public ServiceFramework<IAudioOut> {
 public:
 public:
     explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
     explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
                        size_t session_id, const std::string& device_name,
                        size_t session_id, const std::string& device_name,
-                       const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
+                       const AudioOutParameter& in_params, Kernel::KProcess* handle,
+                       u64 applet_resource_user_id)
         : ServiceFramework{system_, "IAudioOut"}, service_context{system_, "IAudioOut"},
         : ServiceFramework{system_, "IAudioOut"}, service_context{system_, "IAudioOut"},
-          event{service_context.CreateEvent("AudioOutEvent")},
+          event{service_context.CreateEvent("AudioOutEvent")}, process{handle},
           impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
           impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
 
 
         // clang-format off
         // clang-format off
@@ -50,11 +51,14 @@ public:
         };
         };
         // clang-format on
         // clang-format on
         RegisterHandlers(functions);
         RegisterHandlers(functions);
+
+        process->Open();
     }
     }
 
 
     ~IAudioOut() override {
     ~IAudioOut() override {
         impl->Free();
         impl->Free();
         service_context.CloseEvent(event);
         service_context.CloseEvent(event);
+        process->Close();
     }
     }
 
 
     [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
     [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
@@ -206,6 +210,7 @@ private:
 
 
     KernelHelpers::ServiceContext service_context;
     KernelHelpers::ServiceContext service_context;
     Kernel::KEvent* event;
     Kernel::KEvent* event;
+    Kernel::KProcess* process;
     std::shared_ptr<AudioCore::AudioOut::Out> impl;
     std::shared_ptr<AudioCore::AudioOut::Out> impl;
     Common::ScratchBuffer<u64> released_buffer;
     Common::ScratchBuffer<u64> released_buffer;
 };
 };
@@ -257,6 +262,14 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto device_name = Common::StringFromBuffer(device_name_data);
     auto handle{ctx.GetCopyHandle(0)};
     auto handle{ctx.GetCopyHandle(0)};
 
 
+    auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
+    if (process.IsNull()) {
+        LOG_ERROR(Service_Audio, "Failed to get process handle");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
     auto link{impl->LinkToManager()};
     auto link{impl->LinkToManager()};
     if (link.IsError()) {
     if (link.IsError()) {
         LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager");
         LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager");
@@ -276,10 +289,11 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
     LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
     LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
               impl->num_free_sessions);
               impl->num_free_sessions);
 
 
-    auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name,
-                                                 in_params, handle, applet_resource_user_id);
-    result = audio_out->GetImpl()->GetSystem().Initialize(device_name, in_params, handle,
-                                                          applet_resource_user_id);
+    auto audio_out =
+        std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, in_params,
+                                    process.GetPointerUnsafe(), applet_resource_user_id);
+    result = audio_out->GetImpl()->GetSystem().Initialize(
+        device_name, in_params, process.GetPointerUnsafe(), applet_resource_user_id);
     if (result.IsError()) {
     if (result.IsError()) {
         LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
         LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};

+ 50 - 177
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_ldr.h"
 #include "core/hle/service/filesystem/fsp_pr.h"
 #include "core/hle/service/filesystem/fsp_pr.h"
 #include "core/hle/service/filesystem/fsp_srv.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/hle/service/server_manager.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 
 
 namespace Service::FileSystem {
 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,
 static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
                                                        std::string_view dir_name_) {
                                                        std::string_view dir_name_) {
     std::string dir_name(Common::FS::SanitizePath(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;
 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;
     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;
         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;
     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 {
 Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
@@ -540,48 +458,6 @@ u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const {
     return 0;
     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) {
 void FileSystemController::SetGameCard(FileSys::VirtualFile file) {
     gamecard = std::make_unique<FileSys::XCI>(file);
     gamecard = std::make_unique<FileSys::XCI>(file);
     const auto dir = gamecard->ConcatenatedPseudoDirectory();
     const auto dir = gamecard->ConcatenatedPseudoDirectory();
@@ -801,14 +677,9 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
     return bis_factory->GetBCATDirectory(title_id);
     return bis_factory->GetBCATDirectory(title_id);
 }
 }
 
 
-void FileSystemController::SetAutoSaveDataCreation(bool enable) {
-    save_data_factory->SetAutoCreate(enable);
-}
-
 void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
 void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
     if (overwrite) {
     if (overwrite) {
         bis_factory = nullptr;
         bis_factory = nullptr;
-        save_data_factory = nullptr;
         sdmc_factory = nullptr;
         sdmc_factory = nullptr;
     }
     }
 
 
@@ -836,11 +707,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
                                        bis_factory->GetUserNANDContents());
                                        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) {
     if (sdmc_factory == nullptr) {
         sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
         sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
                                                               std::move(sd_load_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) {
 void LoopProcess(Core::System& system) {
     auto server_manager = std::make_unique<ServerManager>(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-ldr", std::make_shared<FSP_LDR>(system));
     server_manager->RegisterNamedService("fsp:pr", std::make_shared<FSP_PR>(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));
     ServerManager::RunServer(std::move(server_manager));
 }
 }
 
 

+ 29 - 30
src/core/hle/service/filesystem/filesystem.h

@@ -43,6 +43,9 @@ class ServiceManager;
 
 
 namespace FileSystem {
 namespace FileSystem {
 
 
+class RomFsController;
+class SaveDataController;
+
 enum class ContentStorageId : u32 {
 enum class ContentStorageId : u32 {
     System,
     System,
     User,
     User,
@@ -61,32 +64,24 @@ enum class OpenDirectoryMode : u64 {
 };
 };
 DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
 DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
 
 
+using ProcessId = u64;
+using ProgramId = u64;
+
 class FileSystemController {
 class FileSystemController {
 public:
 public:
     explicit FileSystemController(Core::System& system_);
     explicit FileSystemController(Core::System& system_);
     ~FileSystemController();
     ~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 OpenSDMC(FileSys::VirtualDir* out_sdmc) const;
     Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
     Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
                             FileSys::BisPartitionId id) const;
                             FileSys::BisPartitionId id) const;
@@ -96,11 +91,6 @@ public:
     u64 GetFreeSpaceSize(FileSys::StorageId id) const;
     u64 GetFreeSpaceSize(FileSys::StorageId id) const;
     u64 GetTotalSpaceSize(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);
     void SetGameCard(FileSys::VirtualFile file);
     FileSys::XCI* GetGameCard() const;
     FileSys::XCI* GetGameCard() const;
 
 
@@ -133,15 +123,24 @@ public:
 
 
     FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
     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
     // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
     // above is called.
     // above is called.
     void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
     void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
 
 
+    void Reset();
+
 private:
 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::SDMCFactory> sdmc_factory;
     std::unique_ptr<FileSys::BISFactory> bis_factory;
     std::unique_ptr<FileSys::BISFactory> bis_factory;
 
 

+ 31 - 24
src/core/hle/service/filesystem/fsp_srv.cpp

@@ -27,6 +27,8 @@
 #include "core/hle/result.h"
 #include "core/hle/result.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/fsp_srv.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/hle_ipc.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/reporter.h"
 #include "core/reporter.h"
@@ -577,9 +579,11 @@ private:
 
 
 class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
 class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
 public:
 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[] = {
         static const FunctionInfo functions[] = {
             {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
             {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
         };
         };
@@ -626,7 +630,7 @@ private:
 
 
     void FindAllSaves(FileSys::SaveDataSpaceId space) {
     void FindAllSaves(FileSys::SaveDataSpaceId space) {
         FileSys::VirtualDir save_root{};
         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) {
         if (result != ResultSuccess || save_root == nullptr) {
             LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
             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.");
     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;
     std::vector<SaveDataInfo> info;
     u64 next_entry_index = 0;
     u64 next_entry_index = 0;
 };
 };
@@ -863,21 +868,20 @@ FSP_SRV::FSP_SRV(Core::System& system_)
     if (Settings::values.enable_fs_access_log) {
     if (Settings::values.enable_fs_access_log) {
         access_log_mode = AccessLogMode::SdCard;
         access_log_mode = AccessLogMode::SdCard;
     }
     }
-
-    // This should be true on creation
-    fsc.SetAutoSaveDataCreation(true);
 }
 }
 
 
 FSP_SRV::~FSP_SRV() = default;
 FSP_SRV::~FSP_SRV() = default;
 
 
 void FSP_SRV::SetCurrentProcess(HLERequestContext& ctx) {
 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);
     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};
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(res);
 }
 }
 
 
 void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
 void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
@@ -916,7 +920,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
               uid[1], uid[0]);
               uid[1], uid[0]);
 
 
     FileSys::VirtualDir save_data_dir{};
     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};
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
     rb.Push(ResultSuccess);
@@ -931,7 +936,8 @@ void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx)
     LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
     LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
 
 
     FileSys::VirtualDir save_data_dir{};
     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};
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
     rb.Push(ResultSuccess);
@@ -950,7 +956,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
     LOG_INFO(Service_FS, "called.");
     LOG_INFO(Service_FS, "called.");
 
 
     FileSys::VirtualDir dir{};
     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) {
     if (result != ResultSuccess) {
         IPC::ResponseBuilder rb{ctx, 2, 0, 0};
         IPC::ResponseBuilder rb{ctx, 2, 0, 0};
         rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
         rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
@@ -1001,7 +1008,7 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(ResultSuccess);
     rb.Push(ResultSuccess);
     rb.PushIpcInterface<ISaveDataInfoReader>(
     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) {
 void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
@@ -1009,8 +1016,8 @@ void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
 
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(ResultSuccess);
     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) {
 void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) {
@@ -1050,7 +1057,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
     LOG_DEBUG(Service_FS, "called");
     LOG_DEBUG(Service_FS, "called");
 
 
     if (!romfs) {
     if (!romfs) {
-        auto current_romfs = fsc.OpenRomFSCurrentProcess();
+        auto current_romfs = romfs_controller->OpenRomFSCurrentProcess();
         if (!current_romfs) {
         if (!current_romfs) {
             // TODO (bunnei): Find the right error code to use here
             // TODO (bunnei): Find the right error code to use here
             LOG_CRITICAL(Service_FS, "no file system interface available!");
             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}",
     LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
               storage_id, unknown, title_id);
               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) {
     if (!data) {
         const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
         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};
     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>(
     auto storage = std::make_shared<IStorage>(
         system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
         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);
     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) {
     if (!patched_romfs) {
         // TODO: Find the right error code to use here
         // 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) {
 void FSP_SRV::DisableAutoSaveDataCreation(HLERequestContext& ctx) {
     LOG_DEBUG(Service_FS, "called");
     LOG_DEBUG(Service_FS, "called");
 
 
-    fsc.SetAutoSaveDataCreation(false);
+    save_data_controller->SetAutoCreate(false);
 
 
     IPC::ResponseBuilder rb{ctx, 2};
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
     rb.Push(ResultSuccess);

+ 6 - 0
src/core/hle/service/filesystem/fsp_srv.h

@@ -17,6 +17,9 @@ class FileSystemBackend;
 
 
 namespace Service::FileSystem {
 namespace Service::FileSystem {
 
 
+class RomFsController;
+class SaveDataController;
+
 enum class AccessLogVersion : u32 {
 enum class AccessLogVersion : u32 {
     V7_0_0 = 2,
     V7_0_0 = 2,
 
 
@@ -67,6 +70,9 @@ private:
     u64 current_process_id = 0;
     u64 current_process_id = 0;
     u32 access_log_program_index = 0;
     u32 access_log_program_index = 0;
     AccessLogMode access_log_mode = AccessLogMode::None;
     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
 } // namespace Service::FileSystem

+ 37 - 0
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

+ 31 - 0
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

+ 99 - 0
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

+ 35 - 0
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

+ 4 - 3
src/core/hle/service/glue/arp.cpp

@@ -15,9 +15,10 @@
 namespace Service::Glue {
 namespace Service::Glue {
 
 
 namespace {
 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;
         return process->GetProcessId() == process_id;
     });
     });
 
 

+ 4 - 6
src/core/hle/service/hid/hid.cpp

@@ -22,12 +22,10 @@ void LoopProcess(Core::System& system) {
     std::shared_ptr<HidFirmwareSettings> firmware_settings =
     std::shared_ptr<HidFirmwareSettings> firmware_settings =
         std::make_shared<HidFirmwareSettings>();
         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(
     server_manager->RegisterNamedService(
         "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));
         "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));

+ 1 - 1
src/core/hle/service/hid/hid_server.cpp

@@ -1498,7 +1498,7 @@ void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
     bool check_device_index = false;
     bool check_device_index = false;
 
 
     switch (vibration_device_handle.npad_type) {
     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::Handheld:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconLeft:
     case Core::HID::NpadStyleIndex::JoyconLeft:

+ 2 - 1
src/core/hle/service/nvnflinger/nvnflinger.cpp

@@ -223,7 +223,8 @@ Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64
         return VI::ResultNotFound;
         return VI::ResultNotFound;
     }
     }
 
 
-    return display->GetVSyncEvent(out_vsync_event);
+    *out_vsync_event = display->GetVSyncEvent();
+    return ResultSuccess;
 }
 }
 
 
 VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
 VI::Display* Nvnflinger::FindDisplay(u64 display_id) {

+ 37 - 48
src/core/hle/service/pm/pm.cpp

@@ -22,27 +22,26 @@ constexpr Result ResultProcessNotFound{ErrorModule::PM, 1};
 
 
 constexpr u64 NO_PROCESS_FOUND_PID{0};
 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);
     const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate);
 
 
     if (iter == process_list.end()) {
     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};
     IPC::ResponseBuilder rb{ctx, 4};
     rb.Push(ResultSuccess);
     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
 } // Anonymous namespace
@@ -80,8 +79,7 @@ private:
 
 
 class DebugMonitor final : public ServiceFramework<DebugMonitor> {
 class DebugMonitor final : public ServiceFramework<DebugMonitor> {
 public:
 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
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, nullptr, "GetJitDebugProcessIdList"},
             {0, nullptr, "GetJitDebugProcessIdList"},
@@ -106,12 +104,11 @@ private:
 
 
         LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
         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};
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             rb.Push(ResultProcessNotFound);
             return;
             return;
@@ -119,12 +116,13 @@ private:
 
 
         IPC::ResponseBuilder rb{ctx, 4};
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProcessId());
+        rb.Push(process->GetProcessId());
     }
     }
 
 
     void GetApplicationProcessId(HLERequestContext& ctx) {
     void GetApplicationProcessId(HLERequestContext& ctx) {
         LOG_DEBUG(Service_PM, "called");
         LOG_DEBUG(Service_PM, "called");
-        GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+        auto list = kernel.GetProcessList();
+        GetApplicationPidGeneric(ctx, list);
     }
     }
 
 
     void AtmosphereGetProcessInfo(HLERequestContext& ctx) {
     void AtmosphereGetProcessInfo(HLERequestContext& ctx) {
@@ -135,11 +133,10 @@ private:
 
 
         LOG_WARNING(Service_PM, "(Partial Implementation) called, pid={:016X}", pid);
         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};
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             rb.Push(ResultProcessNotFound);
             return;
             return;
@@ -159,7 +156,7 @@ private:
 
 
         OverrideStatus override_status{};
         OverrideStatus override_status{};
         ProgramLocation program_location{
         ProgramLocation program_location{
-            .program_id = (*process)->GetProgramId(),
+            .program_id = process->GetProgramId(),
             .storage_id = 0,
             .storage_id = 0,
         };
         };
 
 
@@ -169,14 +166,11 @@ private:
         rb.PushRaw(program_location);
         rb.PushRaw(program_location);
         rb.PushRaw(override_status);
         rb.PushRaw(override_status);
     }
     }
-
-    const Kernel::KernelCore& kernel;
 };
 };
 
 
 class Info final : public ServiceFramework<Info> {
 class Info final : public ServiceFramework<Info> {
 public:
 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[] = {
         static const FunctionInfo functions[] = {
             {0, &Info::GetProgramId, "GetProgramId"},
             {0, &Info::GetProgramId, "GetProgramId"},
             {65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
             {65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
@@ -193,11 +187,11 @@ private:
 
 
         LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id);
         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};
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             rb.Push(ResultProcessNotFound);
             return;
             return;
@@ -205,7 +199,7 @@ private:
 
 
         IPC::ResponseBuilder rb{ctx, 4};
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProgramId());
+        rb.Push(process->GetProgramId());
     }
     }
 
 
     void AtmosphereGetProcessId(HLERequestContext& ctx) {
     void AtmosphereGetProcessId(HLERequestContext& ctx) {
@@ -214,11 +208,11 @@ private:
 
 
         LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
         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};
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             rb.Push(ResultProcessNotFound);
             return;
             return;
@@ -226,16 +220,13 @@ private:
 
 
         IPC::ResponseBuilder rb{ctx, 4};
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProcessId());
+        rb.Push(process->GetProcessId());
     }
     }
-
-    const std::vector<Kernel::KProcess*>& process_list;
 };
 };
 
 
 class Shell final : public ServiceFramework<Shell> {
 class Shell final : public ServiceFramework<Shell> {
 public:
 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
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, nullptr, "LaunchProgram"},
             {0, nullptr, "LaunchProgram"},
@@ -257,10 +248,9 @@ public:
 private:
 private:
     void GetApplicationProcessIdForShell(HLERequestContext& ctx) {
     void GetApplicationProcessIdForShell(HLERequestContext& ctx) {
         LOG_DEBUG(Service_PM, "called");
         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) {
 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:bm", std::make_shared<BootMode>(system));
     server_manager->RegisterNamedService("pm:dmnt", std::make_shared<DebugMonitor>(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));
     server_manager->RegisterNamedService("pm:shell", std::make_shared<Shell>(system));
     ServerManager::RunServer(std::move(server_manager));
     ServerManager::RunServer(std::move(server_manager));
 }
 }

+ 7 - 2
src/core/hle/service/server_manager.cpp

@@ -256,8 +256,13 @@ Result ServerManager::WaitAndProcessImpl() {
 
 
         // Wait for a signal.
         // Wait for a signal.
         s32 out_index{-1};
         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);
         ASSERT(out_index >= 0 && out_index < num_objs);
 
 
         // Set the output index.
         // Set the output index.

+ 1 - 12
src/core/hle/service/vi/display/vi_display.cpp

@@ -71,18 +71,7 @@ size_t Display::GetNumLayers() const {
     return std::ranges::count_if(layers, [](auto& l) { return l->IsOpen(); });
     return std::ranges::count_if(layers, [](auto& l) { return l->IsOpen(); });
 }
 }
 
 
-Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) {
-    if (got_vsync_event) {
-        return ResultPermissionDenied;
-    }
-
-    got_vsync_event = true;
-
-    *out_vsync_event = GetVSyncEventUnchecked();
-    return ResultSuccess;
-}
-
-Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
+Kernel::KReadableEvent* Display::GetVSyncEvent() {
     return &vsync_event->GetReadableEvent();
     return &vsync_event->GetReadableEvent();
 }
 }
 
 

+ 1 - 11
src/core/hle/service/vi/display/vi_display.h

@@ -74,16 +74,8 @@ public:
 
 
     std::size_t GetNumLayers() const;
     std::size_t GetNumLayers() const;
 
 
-    /**
-     * Gets the internal vsync event.
-     *
-     * @returns The internal Vsync event if it has not yet been retrieved,
-     *          VI::ResultPermissionDenied otherwise.
-     */
-    [[nodiscard]] Result GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event);
-
     /// Gets the internal vsync event.
     /// Gets the internal vsync event.
-    Kernel::KReadableEvent* GetVSyncEventUnchecked();
+    Kernel::KReadableEvent* GetVSyncEvent();
 
 
     /// Signals the internal vsync event.
     /// Signals the internal vsync event.
     void SignalVSyncEvent();
     void SignalVSyncEvent();
@@ -104,7 +96,6 @@ public:
     /// Resets the display for a new connection.
     /// Resets the display for a new connection.
     void Reset() {
     void Reset() {
         layers.clear();
         layers.clear();
-        got_vsync_event = false;
     }
     }
 
 
     /// Attempts to find a layer with the given ID.
     /// Attempts to find a layer with the given ID.
@@ -133,7 +124,6 @@ private:
 
 
     std::vector<std::unique_ptr<Layer>> layers;
     std::vector<std::unique_ptr<Layer>> layers;
     Kernel::KEvent* vsync_event{};
     Kernel::KEvent* vsync_event{};
-    bool got_vsync_event{false};
 };
 };
 
 
 } // namespace Service::VI
 } // namespace Service::VI

+ 33 - 26
src/core/hle/service/vi/vi.cpp

@@ -343,8 +343,8 @@ private:
 
 
 class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
 class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
 public:
 public:
-    explicit IManagerDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nv_flinger_)
-        : ServiceFramework{system_, "IManagerDisplayService"}, nv_flinger{nv_flinger_} {
+    explicit IManagerDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
+        : ServiceFramework{system_, "IManagerDisplayService"}, nvnflinger{nvnflinger_} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {200, nullptr, "AllocateProcessHeapBlock"},
             {200, nullptr, "AllocateProcessHeapBlock"},
@@ -440,7 +440,7 @@ private:
         IPC::RequestParser rp{ctx};
         IPC::RequestParser rp{ctx};
         const u64 display = rp.Pop<u64>();
         const u64 display = rp.Pop<u64>();
 
 
-        const Result rc = nv_flinger.CloseDisplay(display) ? ResultSuccess : ResultUnknown;
+        const Result rc = nvnflinger.CloseDisplay(display) ? ResultSuccess : ResultUnknown;
 
 
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(rc);
         rb.Push(rc);
@@ -457,7 +457,7 @@ private:
                     "(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}",
                     "(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}",
                     unknown, display, aruid);
                     unknown, display, aruid);
 
 
-        const auto layer_id = nv_flinger.CreateLayer(display);
+        const auto layer_id = nvnflinger.CreateLayer(display);
         if (!layer_id) {
         if (!layer_id) {
             LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
             LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -494,14 +494,14 @@ private:
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
     }
     }
 
 
-    Nvnflinger::Nvnflinger& nv_flinger;
+    Nvnflinger::Nvnflinger& nvnflinger;
 };
 };
 
 
 class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
 class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
 public:
 public:
-    IApplicationDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nv_flinger_,
+    IApplicationDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_,
                                Nvnflinger::HosBinderDriverServer& hos_binder_driver_server_)
                                Nvnflinger::HosBinderDriverServer& hos_binder_driver_server_)
-        : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_},
+        : ServiceFramework{system_, "IApplicationDisplayService"}, nvnflinger{nvnflinger_},
           hos_binder_driver_server{hos_binder_driver_server_} {
           hos_binder_driver_server{hos_binder_driver_server_} {
 
 
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
@@ -564,7 +564,7 @@ private:
 
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
+        rb.PushIpcInterface<ISystemDisplayService>(system, nvnflinger);
     }
     }
 
 
     void GetManagerDisplayService(HLERequestContext& ctx) {
     void GetManagerDisplayService(HLERequestContext& ctx) {
@@ -572,7 +572,7 @@ private:
 
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IManagerDisplayService>(system, nv_flinger);
+        rb.PushIpcInterface<IManagerDisplayService>(system, nvnflinger);
     }
     }
 
 
     void GetIndirectDisplayTransactionService(HLERequestContext& ctx) {
     void GetIndirectDisplayTransactionService(HLERequestContext& ctx) {
@@ -607,7 +607,7 @@ private:
 
 
         ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
         ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
 
 
-        const auto display_id = nv_flinger.OpenDisplay(name);
+        const auto display_id = nvnflinger.OpenDisplay(name);
         if (!display_id) {
         if (!display_id) {
             LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
             LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -624,7 +624,7 @@ private:
         IPC::RequestParser rp{ctx};
         IPC::RequestParser rp{ctx};
         const u64 display_id = rp.Pop<u64>();
         const u64 display_id = rp.Pop<u64>();
 
 
-        const Result rc = nv_flinger.CloseDisplay(display_id) ? ResultSuccess : ResultUnknown;
+        const Result rc = nvnflinger.CloseDisplay(display_id) ? ResultSuccess : ResultUnknown;
 
 
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(rc);
         rb.Push(rc);
@@ -703,7 +703,7 @@ private:
 
 
         LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}, aruid=0x{:016X}", layer_id, aruid);
         LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}, aruid=0x{:016X}", layer_id, aruid);
 
 
-        const auto display_id = nv_flinger.OpenDisplay(display_name);
+        const auto display_id = nvnflinger.OpenDisplay(display_name);
         if (!display_id) {
         if (!display_id) {
             LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
             LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -711,7 +711,7 @@ private:
             return;
             return;
         }
         }
 
 
-        const auto buffer_queue_id = nv_flinger.FindBufferQueueId(*display_id, layer_id);
+        const auto buffer_queue_id = nvnflinger.FindBufferQueueId(*display_id, layer_id);
         if (!buffer_queue_id) {
         if (!buffer_queue_id) {
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -719,7 +719,7 @@ private:
             return;
             return;
         }
         }
 
 
-        nv_flinger.OpenLayer(layer_id);
+        nvnflinger.OpenLayer(layer_id);
 
 
         android::OutputParcel parcel;
         android::OutputParcel parcel;
         parcel.WriteInterface(NativeWindow{*buffer_queue_id});
         parcel.WriteInterface(NativeWindow{*buffer_queue_id});
@@ -737,7 +737,7 @@ private:
 
 
         LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id);
         LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id);
 
 
-        nv_flinger.CloseLayer(layer_id);
+        nvnflinger.CloseLayer(layer_id);
 
 
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
@@ -753,7 +753,7 @@ private:
 
 
         // TODO(Subv): What's the difference between a Stray and a Managed layer?
         // TODO(Subv): What's the difference between a Stray and a Managed layer?
 
 
-        const auto layer_id = nv_flinger.CreateLayer(display_id);
+        const auto layer_id = nvnflinger.CreateLayer(display_id);
         if (!layer_id) {
         if (!layer_id) {
             LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
             LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -761,7 +761,7 @@ private:
             return;
             return;
         }
         }
 
 
-        const auto buffer_queue_id = nv_flinger.FindBufferQueueId(display_id, *layer_id);
+        const auto buffer_queue_id = nvnflinger.FindBufferQueueId(display_id, *layer_id);
         if (!buffer_queue_id) {
         if (!buffer_queue_id) {
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
             IPC::ResponseBuilder rb{ctx, 2};
             IPC::ResponseBuilder rb{ctx, 2};
@@ -785,7 +785,7 @@ private:
         const u64 layer_id = rp.Pop<u64>();
         const u64 layer_id = rp.Pop<u64>();
 
 
         LOG_WARNING(Service_VI, "(STUBBED) called. layer_id=0x{:016X}", layer_id);
         LOG_WARNING(Service_VI, "(STUBBED) called. layer_id=0x{:016X}", layer_id);
-        nv_flinger.DestroyLayer(layer_id);
+        nvnflinger.DestroyLayer(layer_id);
 
 
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
@@ -798,7 +798,7 @@ private:
         LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
         LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
 
 
         Kernel::KReadableEvent* vsync_event{};
         Kernel::KReadableEvent* vsync_event{};
-        const auto result = nv_flinger.FindVsyncEvent(&vsync_event, display_id);
+        const auto result = nvnflinger.FindVsyncEvent(&vsync_event, display_id);
         if (result != ResultSuccess) {
         if (result != ResultSuccess) {
             if (result == ResultNotFound) {
             if (result == ResultNotFound) {
                 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
                 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
@@ -808,6 +808,12 @@ private:
             rb.Push(result);
             rb.Push(result);
             return;
             return;
         }
         }
+        if (vsync_event_fetched) {
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(VI::ResultPermissionDenied);
+            return;
+        }
+        vsync_event_fetched = true;
 
 
         IPC::ResponseBuilder rb{ctx, 2, 1};
         IPC::ResponseBuilder rb{ctx, 2, 1};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
@@ -899,8 +905,9 @@ private:
         }
         }
     }
     }
 
 
-    Nvnflinger::Nvnflinger& nv_flinger;
+    Nvnflinger::Nvnflinger& nvnflinger;
     Nvnflinger::HosBinderDriverServer& hos_binder_driver_server;
     Nvnflinger::HosBinderDriverServer& hos_binder_driver_server;
+    bool vsync_event_fetched{false};
 };
 };
 
 
 static bool IsValidServiceAccess(Permission permission, Policy policy) {
 static bool IsValidServiceAccess(Permission permission, Policy policy) {
@@ -916,7 +923,7 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) {
 }
 }
 
 
 void detail::GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
 void detail::GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
-                                   Nvnflinger::Nvnflinger& nv_flinger,
+                                   Nvnflinger::Nvnflinger& nvnflinger,
                                    Nvnflinger::HosBinderDriverServer& hos_binder_driver_server,
                                    Nvnflinger::HosBinderDriverServer& hos_binder_driver_server,
                                    Permission permission) {
                                    Permission permission) {
     IPC::RequestParser rp{ctx};
     IPC::RequestParser rp{ctx};
@@ -931,19 +938,19 @@ void detail::GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
 
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(ResultSuccess);
     rb.Push(ResultSuccess);
-    rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger, hos_binder_driver_server);
+    rb.PushIpcInterface<IApplicationDisplayService>(system, nvnflinger, hos_binder_driver_server);
 }
 }
 
 
-void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nv_flinger,
+void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
                  Nvnflinger::HosBinderDriverServer& hos_binder_driver_server) {
                  Nvnflinger::HosBinderDriverServer& hos_binder_driver_server) {
     auto server_manager = std::make_unique<ServerManager>(system);
     auto server_manager = std::make_unique<ServerManager>(system);
 
 
     server_manager->RegisterNamedService(
     server_manager->RegisterNamedService(
-        "vi:m", std::make_shared<VI_M>(system, nv_flinger, hos_binder_driver_server));
+        "vi:m", std::make_shared<VI_M>(system, nvnflinger, hos_binder_driver_server));
     server_manager->RegisterNamedService(
     server_manager->RegisterNamedService(
-        "vi:s", std::make_shared<VI_S>(system, nv_flinger, hos_binder_driver_server));
+        "vi:s", std::make_shared<VI_S>(system, nvnflinger, hos_binder_driver_server));
     server_manager->RegisterNamedService(
     server_manager->RegisterNamedService(
-        "vi:u", std::make_shared<VI_U>(system, nv_flinger, hos_binder_driver_server));
+        "vi:u", std::make_shared<VI_U>(system, nvnflinger, hos_binder_driver_server));
     ServerManager::RunServer(std::move(server_manager));
     ServerManager::RunServer(std::move(server_manager));
 }
 }
 
 

+ 1 - 1
src/core/hle/service/vi/vi.h

@@ -48,7 +48,7 @@ void GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
                            Permission permission);
                            Permission permission);
 } // namespace detail
 } // namespace detail
 
 
-void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nv_flinger,
+void LoopProcess(Core::System& system, Nvnflinger::Nvnflinger& nvnflinger,
                  Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
                  Nvnflinger::HosBinderDriverServer& hos_binder_driver_server);
 
 
 } // namespace Service::VI
 } // namespace Service::VI

+ 57 - 32
src/core/loader/deconstructed_rom_directory.cpp

@@ -19,8 +19,54 @@
 #include "core/arm/nce/patcher.h"
 #include "core/arm/nce/patcher.h"
 #endif
 #endif
 
 
+#ifndef HAS_NCE
+namespace Core::NCE {
+class Patcher {};
+} // namespace Core::NCE
+#endif
+
 namespace Loader {
 namespace Loader {
 
 
+struct PatchCollection {
+    explicit PatchCollection(bool is_application_) : is_application{is_application_} {
+        module_patcher_indices.fill(-1);
+        patchers.emplace_back();
+    }
+
+    std::vector<Core::NCE::Patcher>* GetPatchers() {
+        if (is_application && Settings::IsNceEnabled()) {
+            return &patchers;
+        }
+        return nullptr;
+    }
+
+    size_t GetTotalPatchSize() const {
+        size_t total_size{};
+#ifdef HAS_NCE
+        for (auto& patcher : patchers) {
+            total_size += patcher.GetSectionSize();
+        }
+#endif
+        return total_size;
+    }
+
+    void SaveIndex(size_t module) {
+        module_patcher_indices[module] = static_cast<s32>(patchers.size() - 1);
+    }
+
+    s32 GetIndex(size_t module) const {
+        return module_patcher_indices[module];
+    }
+
+    s32 GetLastIndex() const {
+        return static_cast<s32>(patchers.size()) - 1;
+    }
+
+    bool is_application;
+    std::vector<Core::NCE::Patcher> patchers;
+    std::array<s32, 13> module_patcher_indices{};
+};
+
 AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
 AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
                                                                          bool override_update_)
                                                                          bool override_update_)
     : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
     : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
@@ -142,18 +188,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
     std::size_t code_size{};
     std::size_t code_size{};
 
 
     // Define an nce patch context for each potential module.
     // Define an nce patch context for each potential module.
-#ifdef HAS_NCE
-    std::array<Core::NCE::Patcher, 13> module_patchers;
-#endif
-
-    const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* {
-#ifdef HAS_NCE
-        if (is_application && Settings::IsNceEnabled()) {
-            return &module_patchers[i];
-        }
-#endif
-        return nullptr;
-    };
+    PatchCollection patch_ctx{is_application};
 
 
     // Use the NSO module loader to figure out the code layout
     // Use the NSO module loader to figure out the code layout
     for (size_t i = 0; i < static_modules.size(); i++) {
     for (size_t i = 0; i < static_modules.size(); i++) {
@@ -164,13 +199,14 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         }
         }
 
 
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
-        const auto tentative_next_load_addr =
-            AppLoader_NSO::LoadModule(process, system, *module_file, code_size,
-                                      should_pass_arguments, false, {}, GetPatcher(i));
+        const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+            process, system, *module_file, code_size, should_pass_arguments, false, {},
+            patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
         if (!tentative_next_load_addr) {
         if (!tentative_next_load_addr) {
             return {ResultStatus::ErrorLoadingNSO, {}};
             return {ResultStatus::ErrorLoadingNSO, {}};
         }
         }
 
 
+        patch_ctx.SaveIndex(i);
         code_size = *tentative_next_load_addr;
         code_size = *tentative_next_load_addr;
     }
     }
 
 
@@ -184,6 +220,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         return 0;
         return 0;
     }();
     }();
 
 
+    // Add patch size to the total module size
+    code_size += patch_ctx.GetTotalPatchSize();
+
     // Setup the process code layout
     // Setup the process code layout
     if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
     if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
         return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
         return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
@@ -204,9 +243,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
 
 
         const VAddr load_addr{next_load_addr};
         const VAddr load_addr{next_load_addr};
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
         const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
-        const auto tentative_next_load_addr =
-            AppLoader_NSO::LoadModule(process, system, *module_file, load_addr,
-                                      should_pass_arguments, true, pm, GetPatcher(i));
+        const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+            process, system, *module_file, load_addr, should_pass_arguments, true, pm,
+            patch_ctx.GetPatchers(), patch_ctx.GetIndex(i));
         if (!tentative_next_load_addr) {
         if (!tentative_next_load_addr) {
             return {ResultStatus::ErrorLoadingNSO, {}};
             return {ResultStatus::ErrorLoadingNSO, {}};
         }
         }
@@ -216,20 +255,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
         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;
     is_loaded = true;
     return {ResultStatus::Success,
     return {ResultStatus::Success,
             LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}};
             LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}};

+ 4 - 2
src/core/loader/nca.cpp

@@ -74,8 +74,10 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
         return load_result;
         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;
     is_loaded = true;
     return load_result;
     return load_result;

+ 6 - 4
src/core/loader/nro.cpp

@@ -275,10 +275,12 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
         return {ResultStatus::ErrorLoadingNRO, {}};
         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;
     is_loaded = true;
     return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
     return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,

+ 21 - 20
src/core/loader/nso.cpp

@@ -77,7 +77,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
                                                const FileSys::VfsFile& nso_file, VAddr load_base,
                                                const FileSys::VfsFile& nso_file, VAddr load_base,
                                                bool should_pass_arguments, bool load_into_process,
                                                bool should_pass_arguments, bool load_into_process,
                                                std::optional<FileSys::PatchManager> pm,
                                                std::optional<FileSys::PatchManager> pm,
-                                               Core::NCE::Patcher* patch) {
+                                               std::vector<Core::NCE::Patcher>* patches,
+                                               s32 patch_index) {
     if (nso_file.GetSize() < sizeof(NSOHeader)) {
     if (nso_file.GetSize() < sizeof(NSOHeader)) {
         return std::nullopt;
         return std::nullopt;
     }
     }
@@ -94,8 +95,11 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
     // Allocate some space at the beginning if we are patching in PreText mode.
     // Allocate some space at the beginning if we are patching in PreText mode.
     const size_t module_start = [&]() -> size_t {
     const size_t module_start = [&]() -> size_t {
 #ifdef HAS_NCE
 #ifdef HAS_NCE
-        if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
-            return patch->GetSectionSize();
+        if (patches && load_into_process) {
+            auto* patch = &patches->operator[](patch_index);
+            if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
+                return patch->GetSectionSize();
+            }
         }
         }
 #endif
 #endif
         return 0;
         return 0;
@@ -160,27 +164,24 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
 #ifdef HAS_NCE
 #ifdef HAS_NCE
     // If we are computing the process code layout and using nce backend, patch.
     // If we are computing the process code layout and using nce backend, patch.
     const auto& code = codeset.CodeSegment();
     const auto& code = codeset.CodeSegment();
-    if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) {
+    auto* patch = patches ? &patches->operator[](patch_index) : nullptr;
+    if (patch && !load_into_process) {
         // Patch SVCs and MRS calls in the guest code
         // Patch SVCs and MRS calls in the guest code
-        patch->PatchText(program_image, code);
-
-        // Add patch section size to the module size.
-        image_size += static_cast<u32>(patch->GetSectionSize());
+        while (!patch->PatchText(program_image, code)) {
+            patch = &patches->emplace_back();
+        }
     } else if (patch) {
     } else if (patch) {
         // Relocate code patch and copy to the program_image.
         // Relocate code patch and copy to the program_image.
-        patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers());
-
-        // Update patch section.
-        auto& patch_segment = codeset.PatchSegment();
-        patch_segment.addr =
-            patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
-        patch_segment.size = static_cast<u32>(patch->GetSectionSize());
-
-        // Add patch section size to the module size. In PreText mode image_size
-        // already contains the patch segment as part of module_start.
-        if (patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
-            image_size += patch_segment.size;
+        if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
+            // Update patch section.
+            auto& patch_segment = codeset.PatchSegment();
+            patch_segment.addr =
+                patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
+            patch_segment.size = static_cast<u32>(patch->GetSectionSize());
         }
         }
+
+        // Refresh image_size to take account the patch section if it was added by RelocateAndCopy
+        image_size = static_cast<u32>(program_image.size());
     }
     }
 #endif
 #endif
 
 

+ 2 - 1
src/core/loader/nso.h

@@ -93,7 +93,8 @@ public:
                                            const FileSys::VfsFile& nso_file, VAddr load_base,
                                            const FileSys::VfsFile& nso_file, VAddr load_base,
                                            bool should_pass_arguments, bool load_into_process,
                                            bool should_pass_arguments, bool load_into_process,
                                            std::optional<FileSys::PatchManager> pm = {},
                                            std::optional<FileSys::PatchManager> pm = {},
-                                           Core::NCE::Patcher* patch = nullptr);
+                                           std::vector<Core::NCE::Patcher>* patches = nullptr,
+                                           s32 patch_index = -1);
 
 
     LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
     LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
 
 

+ 2 - 1
src/core/loader/nsp.cpp

@@ -111,7 +111,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
 
 
     FileSys::VirtualFile update_raw;
     FileSys::VirtualFile update_raw;
     if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
     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;
     is_loaded = true;

+ 2 - 1
src/core/loader/xci.cpp

@@ -78,7 +78,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
 
 
     FileSys::VirtualFile update_raw;
     FileSys::VirtualFile update_raw;
     if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
     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;
     is_loaded = true;

+ 34 - 0
src/hid_core/CMakeLists.txt

@@ -36,6 +36,30 @@ add_library(hid_core STATIC
     irsensor/processor_base.h
     irsensor/processor_base.h
     irsensor/tera_plugin_processor.cpp
     irsensor/tera_plugin_processor.cpp
     irsensor/tera_plugin_processor.h
     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.cpp
     resources/debug_pad/debug_pad.h
     resources/debug_pad/debug_pad.h
     resources/debug_pad/debug_pad_types.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.cpp
     resources/npad/npad_resource.h
     resources/npad/npad_resource.h
     resources/npad/npad_types.h
     resources/npad/npad_types.h
+    resources/npad/npad_vibration.cpp
+    resources/npad/npad_vibration.h
     resources/palma/palma.cpp
     resources/palma/palma.cpp
     resources/palma/palma.h
     resources/palma/palma.h
     resources/six_axis/console_six_axis.cpp
     resources/six_axis/console_six_axis.cpp
@@ -78,6 +104,14 @@ add_library(hid_core STATIC
     resources/touch_screen/touch_types.h
     resources/touch_screen/touch_types.h
     resources/unique_pad/unique_pad.cpp
     resources/unique_pad/unique_pad.cpp
     resources/unique_pad/unique_pad.h
     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.cpp
     resources/applet_resource.h
     resources/applet_resource.h
     resources/controller_base.cpp
     resources/controller_base.cpp

+ 11 - 11
src/hid_core/frontend/emulated_controller.cpp

@@ -27,7 +27,7 @@ EmulatedController::~EmulatedController() = default;
 NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
 NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
     switch (type) {
     switch (type) {
     case Settings::ControllerType::ProController:
     case Settings::ControllerType::ProController:
-        return NpadStyleIndex::ProController;
+        return NpadStyleIndex::Fullkey;
     case Settings::ControllerType::DualJoyconDetached:
     case Settings::ControllerType::DualJoyconDetached:
         return NpadStyleIndex::JoyconDual;
         return NpadStyleIndex::JoyconDual;
     case Settings::ControllerType::LeftJoycon:
     case Settings::ControllerType::LeftJoycon:
@@ -49,13 +49,13 @@ NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerTyp
     case Settings::ControllerType::SegaGenesis:
     case Settings::ControllerType::SegaGenesis:
         return NpadStyleIndex::SegaGenesis;
         return NpadStyleIndex::SegaGenesis;
     default:
     default:
-        return NpadStyleIndex::ProController;
+        return NpadStyleIndex::Fullkey;
     }
     }
 }
 }
 
 
 Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
 Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
     switch (type) {
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
         return Settings::ControllerType::ProController;
         return Settings::ControllerType::ProController;
     case NpadStyleIndex::JoyconDual:
     case NpadStyleIndex::JoyconDual:
         return Settings::ControllerType::DualJoyconDetached;
         return Settings::ControllerType::DualJoyconDetached;
@@ -106,7 +106,7 @@ void EmulatedController::ReloadFromSettings() {
         SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
         SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
         original_npad_type = npad_type;
         original_npad_type = npad_type;
     } else {
     } else {
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         original_npad_type = npad_type;
         original_npad_type = npad_type;
     }
     }
 
 
@@ -1073,7 +1073,7 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
         .body = GetNpadColor(controller.color_values[index].body),
         .body = GetNpadColor(controller.color_values[index].body),
         .button = GetNpadColor(controller.color_values[index].buttons),
         .button = GetNpadColor(controller.color_values[index].buttons),
     };
     };
-    if (npad_type == NpadStyleIndex::ProController) {
+    if (npad_type == NpadStyleIndex::Fullkey) {
         controller.colors_state.left = {
         controller.colors_state.left = {
             .body = GetNpadColor(controller.color_values[index].left_grip),
             .body = GetNpadColor(controller.color_values[index].left_grip),
             .button = GetNpadColor(controller.color_values[index].buttons),
             .button = GetNpadColor(controller.color_values[index].buttons),
@@ -1356,7 +1356,7 @@ bool EmulatedController::HasNfc() const {
     switch (npad_type) {
     switch (npad_type) {
     case NpadStyleIndex::JoyconRight:
     case NpadStyleIndex::JoyconRight:
     case NpadStyleIndex::JoyconDual:
     case NpadStyleIndex::JoyconDual:
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
     case NpadStyleIndex::Handheld:
     case NpadStyleIndex::Handheld:
         break;
         break;
     default:
     default:
@@ -1548,7 +1548,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
     // Fallback Fullkey controllers to Pro controllers
     // Fallback Fullkey controllers to Pro controllers
     if (IsControllerFullkey() && supported_style_tag.fullkey) {
     if (IsControllerFullkey() && supported_style_tag.fullkey) {
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         Connect();
         Connect();
         return;
         return;
     }
     }
@@ -1556,13 +1556,13 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
     // Fallback Dual joycon controllers to Pro controllers
     // Fallback Dual joycon controllers to Pro controllers
     if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
     if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         Connect();
         Connect();
         return;
         return;
     }
     }
 
 
     // Fallback Pro controllers to Dual joycon
     // 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);
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
         SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
         SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
         Connect();
         Connect();
@@ -1577,7 +1577,7 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
     std::scoped_lock lock{mutex};
     std::scoped_lock lock{mutex};
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     switch (type) {
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
     case NpadStyleIndex::GameCube:
     case NpadStyleIndex::GameCube:
     case NpadStyleIndex::NES:
     case NpadStyleIndex::NES:
     case NpadStyleIndex::SNES:
     case NpadStyleIndex::SNES:
@@ -1593,7 +1593,7 @@ bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
     std::scoped_lock lock{mutex};
     std::scoped_lock lock{mutex};
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     switch (type) {
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
         return supported_style_tag.fullkey.As<bool>();
         return supported_style_tag.fullkey.As<bool>();
     case NpadStyleIndex::Handheld:
     case NpadStyleIndex::Handheld:
         return supported_style_tag.handheld.As<bool>();
         return supported_style_tag.handheld.As<bool>();

+ 2 - 1
src/hid_core/hid_types.h

@@ -220,6 +220,7 @@ enum class NpadIdType : u32 {
 };
 };
 
 
 enum class NpadInterfaceType : u8 {
 enum class NpadInterfaceType : u8 {
+    None = 0,
     Bluetooth = 1,
     Bluetooth = 1,
     Rail = 2,
     Rail = 2,
     Usb = 3,
     Usb = 3,
@@ -229,7 +230,7 @@ enum class NpadInterfaceType : u8 {
 // This is nn::hid::NpadStyleIndex
 // This is nn::hid::NpadStyleIndex
 enum class NpadStyleIndex : u8 {
 enum class NpadStyleIndex : u8 {
     None = 0,
     None = 0,
-    ProController = 3,
+    Fullkey = 3,
     Handheld = 4,
     Handheld = 4,
     HandheldNES = 4,
     HandheldNES = 4,
     JoyconDual = 5,
     JoyconDual = 5,

+ 1 - 1
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) {
 constexpr Result IsVibrationHandleValid(const Core::HID::VibrationDeviceHandle& handle) {
     switch (handle.npad_type) {
     switch (handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconLeft:
     case Core::HID::NpadStyleIndex::JoyconLeft:

+ 197 - 0
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

+ 49 - 0
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

+ 199 - 0
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

+ 75 - 0
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

Some files were not shown because too many files changed in this diff