From ae88ea79b24b658cf3f176370f209393d64f83bd Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 1 Jan 2024 12:38:20 -0500
Subject: [PATCH 01/33] vi: fix name of nvnflinger

---
 src/core/hle/service/vi/vi.cpp | 52 +++++++++++++++++-----------------
 src/core/hle/service/vi/vi.h   |  2 +-
 2 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 9ab8788e3..29471abfe 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -343,8 +343,8 @@ private:
 
 class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
 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
         static const FunctionInfo functions[] = {
             {200, nullptr, "AllocateProcessHeapBlock"},
@@ -440,7 +440,7 @@ private:
         IPC::RequestParser rp{ctx};
         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};
         rb.Push(rc);
@@ -457,7 +457,7 @@ private:
                     "(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}",
                     unknown, display, aruid);
 
-        const auto layer_id = nv_flinger.CreateLayer(display);
+        const auto layer_id = nvnflinger.CreateLayer(display);
         if (!layer_id) {
             LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -494,14 +494,14 @@ private:
         rb.Push(ResultSuccess);
     }
 
-    Nvnflinger::Nvnflinger& nv_flinger;
+    Nvnflinger::Nvnflinger& nvnflinger;
 };
 
 class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
 public:
-    IApplicationDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nv_flinger_,
+    IApplicationDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_,
                                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_} {
 
         static const FunctionInfo functions[] = {
@@ -564,7 +564,7 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(ResultSuccess);
-        rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
+        rb.PushIpcInterface<ISystemDisplayService>(system, nvnflinger);
     }
 
     void GetManagerDisplayService(HLERequestContext& ctx) {
@@ -572,7 +572,7 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IManagerDisplayService>(system, nv_flinger);
+        rb.PushIpcInterface<IManagerDisplayService>(system, nvnflinger);
     }
 
     void GetIndirectDisplayTransactionService(HLERequestContext& ctx) {
@@ -607,7 +607,7 @@ private:
 
         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) {
             LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -624,7 +624,7 @@ private:
         IPC::RequestParser rp{ctx};
         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};
         rb.Push(rc);
@@ -703,7 +703,7 @@ private:
 
         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) {
             LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -711,7 +711,7 @@ private:
             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) {
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -719,7 +719,7 @@ private:
             return;
         }
 
-        nv_flinger.OpenLayer(layer_id);
+        nvnflinger.OpenLayer(layer_id);
 
         android::OutputParcel parcel;
         parcel.WriteInterface(NativeWindow{*buffer_queue_id});
@@ -737,7 +737,7 @@ private:
 
         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};
         rb.Push(ResultSuccess);
@@ -753,7 +753,7 @@ private:
 
         // 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) {
             LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -761,7 +761,7 @@ private:
             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) {
             LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
             IPC::ResponseBuilder rb{ctx, 2};
@@ -785,7 +785,7 @@ private:
         const u64 layer_id = rp.Pop<u64>();
 
         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};
         rb.Push(ResultSuccess);
@@ -798,7 +798,7 @@ private:
         LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
 
         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 == ResultNotFound) {
                 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
@@ -899,7 +899,7 @@ private:
         }
     }
 
-    Nvnflinger::Nvnflinger& nv_flinger;
+    Nvnflinger::Nvnflinger& nvnflinger;
     Nvnflinger::HosBinderDriverServer& hos_binder_driver_server;
 };
 
@@ -916,7 +916,7 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) {
 }
 
 void detail::GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
-                                   Nvnflinger::Nvnflinger& nv_flinger,
+                                   Nvnflinger::Nvnflinger& nvnflinger,
                                    Nvnflinger::HosBinderDriverServer& hos_binder_driver_server,
                                    Permission permission) {
     IPC::RequestParser rp{ctx};
@@ -931,19 +931,19 @@ void detail::GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     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) {
     auto server_manager = std::make_unique<ServerManager>(system);
 
     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(
-        "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(
-        "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));
 }
 
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index a35b62f97..ee4bcbcfa 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -48,7 +48,7 @@ void GetDisplayServiceImpl(HLERequestContext& ctx, Core::System& system,
                            Permission permission);
 } // 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);
 
 } // namespace Service::VI

From 200b371d13bb5919ce68d72c760b86313bec3b0f Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Tue, 2 Jan 2024 18:23:57 -0500
Subject: [PATCH 02/33] server_manager: respond to session close correctly

---
 src/core/hle/service/server_manager.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 15edb23e0..8ef49387d 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -256,8 +256,13 @@ Result ServerManager::WaitAndProcessImpl() {
 
         // Wait for a signal.
         s32 out_index{-1};
-        R_TRY(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index, wait_objs.data(),
-                                                   num_objs, -1));
+        R_TRY_CATCH(Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index,
+                                                         wait_objs.data(), num_objs, -1)) {
+            R_CATCH(Kernel::ResultSessionClosed) {
+                // On session closed, index is updated and we don't want to return an error.
+            }
+        }
+        R_END_TRY_CATCH;
         ASSERT(out_index >= 0 && out_index < num_objs);
 
         // Set the output index.

From ea710e652398f0646aeb1a1cdf78cee9691440e8 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sat, 6 Jan 2024 23:58:04 -0500
Subject: [PATCH 03/33] vi: connect vsync event handle lifetime to application
 display service interface

---
 src/core/hle/service/nvnflinger/nvnflinger.cpp |  3 ++-
 src/core/hle/service/vi/display/vi_display.cpp | 13 +------------
 src/core/hle/service/vi/display/vi_display.h   | 12 +-----------
 src/core/hle/service/vi/vi.cpp                 |  7 +++++++
 4 files changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index aa8aaa2d9..0469110e8 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -223,7 +223,8 @@ Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64
         return VI::ResultNotFound;
     }
 
-    return display->GetVSyncEvent(out_vsync_event);
+    *out_vsync_event = display->GetVSyncEvent();
+    return ResultSuccess;
 }
 
 VI::Display* Nvnflinger::FindDisplay(u64 display_id) {
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 71ce9be50..e2d9cd98a 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/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(); });
 }
 
-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();
 }
 
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 1d9360b96..7e68ee79b 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -74,16 +74,8 @@ public:
 
     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.
-    Kernel::KReadableEvent* GetVSyncEventUnchecked();
+    Kernel::KReadableEvent* GetVSyncEvent();
 
     /// Signals the internal vsync event.
     void SignalVSyncEvent();
@@ -104,7 +96,6 @@ public:
     /// Resets the display for a new connection.
     void Reset() {
         layers.clear();
-        got_vsync_event = false;
     }
 
     /// Attempts to find a layer with the given ID.
@@ -133,7 +124,6 @@ private:
 
     std::vector<std::unique_ptr<Layer>> layers;
     Kernel::KEvent* vsync_event{};
-    bool got_vsync_event{false};
 };
 
 } // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 29471abfe..39d5be90d 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -808,6 +808,12 @@ private:
             rb.Push(result);
             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};
         rb.Push(ResultSuccess);
@@ -901,6 +907,7 @@ private:
 
     Nvnflinger::Nvnflinger& nvnflinger;
     Nvnflinger::HosBinderDriverServer& hos_binder_driver_server;
+    bool vsync_event_fetched{false};
 };
 
 static bool IsValidServiceAccess(Permission permission, Policy policy) {

From 4f83b00f6f98cff94ccdec7bbcd664e576bd878e Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 8 Jan 2024 09:34:16 -0500
Subject: [PATCH 04/33] general: fix trailing whitespace

---
 .ci/scripts/linux/exec.sh       | 2 +-
 LICENSES/BSD-2-Clause.txt       | 2 +-
 LICENSES/BSD-3-Clause.txt       | 2 +-
 LICENSES/MPL-2.0.txt            | 2 +-
 externals/ffmpeg/CMakeLists.txt | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.ci/scripts/linux/exec.sh b/.ci/scripts/linux/exec.sh
index fa3d78cc2..04e2486a1 100644
--- a/.ci/scripts/linux/exec.sh
+++ b/.ci/scripts/linux/exec.sh
@@ -9,7 +9,7 @@ chmod a+x ./.ci/scripts/linux/docker.sh
 sudo chown -R 1027 ./
 
 # 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
 
 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"
diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt
index 5f662b354..eb3c575b8 100644
--- a/LICENSES/BSD-2-Clause.txt
+++ b/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:
 
diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt
index ea890afbc..086d3992c 100644
--- a/LICENSES/BSD-3-Clause.txt
+++ b/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:
 
diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt
index 14e2f777f..a612ad981 100644
--- a/LICENSES/MPL-2.0.txt
+++ b/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.
 
 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.
 
 1.8. "License"
diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt
index f2886eb6c..543585d4f 100644
--- a/externals/ffmpeg/CMakeLists.txt
+++ b/externals/ffmpeg/CMakeLists.txt
@@ -138,7 +138,7 @@ if (NOT WIN32 AND NOT ANDROID)
             --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
             --sysroot=${SYSROOT}
             --target-os=android
-            --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" 
+            --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
             --extra-ldflags="-nostdlib"
         )
     endif()

From 30743eff5669a5557b1c00424f84865b8b3ff6ad Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 8 Jan 2024 09:15:19 -0500
Subject: [PATCH 05/33] ci: make verify format workflow output more helpful

---
 .ci/scripts/format/script.sh | 35 +++++++++++++++--------------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index 25b0718f0..c9c5e4fac 100755
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -3,38 +3,33 @@
 # SPDX-FileCopyrightText: 2019 yuzu Emulator Project
 # SPDX-License-Identifier: GPL-2.0-or-later
 
-if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
+shopt -s nullglob globstar
+
+if grep -nrI '\s$' src **/*.yml **/*.txt **/*.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
                  dist/*.svg dist/*.xml; then
     echo Trailing whitespace found, aborting
     exit 1
 fi
 
 # 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
 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
 
-set -x
+DIFF=$(git diff)
 
-if [ "$fail" = 1 ]; then
+if [ ! -z "$DIFF" ]; then
+    echo "!!! Not compliant to coding style, here is the fix:"
+    echo "$DIFF"
     exit 1
 fi

From d99830b59c83bd029f16a93e73b872979fed2e6f Mon Sep 17 00:00:00 2001
From: lat9nq <22451773+lat9nq@users.noreply.github.com>
Date: Tue, 9 Jan 2024 17:29:38 -0500
Subject: [PATCH 06/33] externals: Update txdb_to_nx

Includes a fix lat9nq/tzdb_to_nx@1e82342 that fixes a build issue on Mac OS.
---
 externals/nx_tzdb/tzdb_to_nx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx
index f6680093b..404d39004 160000
--- a/externals/nx_tzdb/tzdb_to_nx
+++ b/externals/nx_tzdb/tzdb_to_nx
@@ -1 +1 @@
-Subproject commit f6680093bca30265c161581fd813d4ddd33f1e3e
+Subproject commit 404d39004570a26c734a9d1fa29ab4d63089c599

From e11a3414ae7114954345512e75583d0cf199c8c6 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Wed, 10 Jan 2024 11:52:58 -0500
Subject: [PATCH 07/33] ci: fix format task

---
 .ci/scripts/format/script.sh | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index c9c5e4fac..572fa9ffb 100755
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -5,8 +5,7 @@
 
 shopt -s nullglob globstar
 
-if grep -nrI '\s$' src **/*.yml **/*.txt **/*.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
-                 dist/*.svg dist/*.xml; then
+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
     exit 1
 fi

From 51ad2d10de80393371a0e1b92e107c90231d0f72 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Mon, 8 Jan 2024 00:16:28 -0500
Subject: [PATCH 08/33] android: Create generic adapter and viewholder

Eliminates repeated code associated with every async differ list
---
 .../yuzu_emu/adapters/AbstractDiffAdapter.kt  | 33 +++++++++++++++++++
 .../yuzu_emu/viewholder/AbstractViewHolder.kt | 18 ++++++++++
 2 files changed, 51 insertions(+)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
new file mode 100644
index 000000000..f006f9e3d
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractDiffAdapter.kt
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate
+ * code used in every [RecyclerView].
+ * Type assigned to [Model] must inherit from [Object] in order to be compared properly.
+ */
+abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> :
+    ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) {
+    override fun onBindViewHolder(holder: Holder, position: Int) =
+        holder.bind(currentList[position])
+
+    private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() {
+        override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
+            return oldItem === newItem
+        }
+
+        @SuppressLint("DiffUtilEquals")
+        override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
+            return oldItem == newItem
+        }
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
new file mode 100644
index 000000000..7101ad434
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholder/AbstractViewHolder.kt
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.viewholder
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+import org.yuzu.yuzu_emu.adapters.AbstractDiffAdapter
+import org.yuzu.yuzu_emu.adapters.AbstractListAdapter
+
+/**
+ * [RecyclerView.ViewHolder] meant to work together with a [AbstractDiffAdapter] or a
+ * [AbstractListAdapter] so we can run [bind] on each list item without needing a manual hookup.
+ */
+abstract class AbstractViewHolder<Model>(binding: ViewBinding) :
+    RecyclerView.ViewHolder(binding.root) {
+    abstract fun bind(model: Model)
+}

From 78c323c4eb1beb502f1c2b5367ecb8654181c1e0 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Mon, 8 Jan 2024 00:17:01 -0500
Subject: [PATCH 09/33] android: Refactor async diff adapters to use
 AbstractDiffAdapter

---
 .../yuzu/yuzu_emu/adapters/AddonAdapter.kt    |  36 +---
 .../yuzu/yuzu_emu/adapters/FolderAdapter.kt   |  36 +---
 .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt | 194 +++++++-----------
 3 files changed, 93 insertions(+), 173 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
index 15c7ca3c9..94c151325 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
@@ -5,48 +5,28 @@ package org.yuzu.yuzu_emu.adapters
 
 import android.view.LayoutInflater
 import android.view.ViewGroup
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
 import org.yuzu.yuzu_emu.model.Addon
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
-    AsyncDifferConfig.Builder(DiffCallback()).build()
-) {
+class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
         ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
             .also { return AddonViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = currentList.size
-
-    override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
-        holder.bind(currentList[position])
-
     inner class AddonViewHolder(val binding: ListItemAddonBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        fun bind(addon: Addon) {
+        AbstractViewHolder<Addon>(binding) {
+        override fun bind(model: Addon) {
             binding.root.setOnClickListener {
                 binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
             }
-            binding.title.text = addon.title
-            binding.version.text = addon.version
+            binding.title.text = model.title
+            binding.version.text = model.version
             binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
-                addon.enabled = checked
+                model.enabled = checked
             }
-            binding.addonSwitch.isChecked = addon.enabled
-        }
-    }
-
-    private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
-        override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
-            return oldItem == newItem
-        }
-
-        override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
-            return oldItem == newItem
+            binding.addonSwitch.isChecked = model.enabled
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
index ab657a7b9..3d8f0bda8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -8,19 +8,14 @@ import android.text.TextUtils
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import androidx.fragment.app.FragmentActivity
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.databinding.CardFolderBinding
 import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
 import org.yuzu.yuzu_emu.model.GameDir
 import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
-    ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
-        AsyncDifferConfig.Builder(DiffCallback()).build()
-    ) {
+    AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
@@ -29,18 +24,11 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
             .also { return FolderViewHolder(it) }
     }
 
-    override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
-        holder.bind(currentList[position])
-
     inner class FolderViewHolder(val binding: CardFolderBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        private lateinit var gameDir: GameDir
-
-        fun bind(gameDir: GameDir) {
-            this.gameDir = gameDir
-
+        AbstractViewHolder<GameDir>(binding) {
+        override fun bind(model: GameDir) {
             binding.apply {
-                path.text = Uri.parse(gameDir.uriString).path
+                path.text = Uri.parse(model.uriString).path
                 path.postDelayed(
                     {
                         path.isSelected = true
@@ -50,7 +38,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
                 )
 
                 buttonEdit.setOnClickListener {
-                    GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
+                    GameFolderPropertiesDialogFragment.newInstance(model)
                         .show(
                             activity.supportFragmentManager,
                             GameFolderPropertiesDialogFragment.TAG
@@ -58,19 +46,9 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
                 }
 
                 buttonDelete.setOnClickListener {
-                    gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
+                    gamesViewModel.removeFolder(model)
                 }
             }
         }
     }
-
-    private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
-        override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
-            return oldItem == newItem
-        }
-
-        override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
-            return oldItem == newItem
-        }
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index a578f0de8..e26c2e0ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -9,7 +9,6 @@ import android.graphics.drawable.LayerDrawable
 import android.net.Uri
 import android.text.TextUtils
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.Toast
@@ -25,10 +24,6 @@ import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import androidx.navigation.findNavController
 import androidx.preference.PreferenceManager
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -36,122 +31,26 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.activities.EmulationActivity
-import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
 import org.yuzu.yuzu_emu.databinding.CardGameBinding
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.utils.GameIconUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class GameAdapter(private val activity: AppCompatActivity) :
-    ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
-    View.OnClickListener,
-    View.OnLongClickListener {
+    AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
-        // Create a new view.
-        val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        binding.cardGame.setOnClickListener(this)
-        binding.cardGame.setOnLongClickListener(this)
-
-        // Use that view to create a ViewHolder.
-        return GameViewHolder(binding)
-    }
-
-    override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
-        holder.bind(currentList[position])
-
-    override fun getItemCount(): Int = currentList.size
-
-    /**
-     * Launches the game that was clicked on.
-     *
-     * @param view The card representing the game the user wants to play.
-     */
-    override fun onClick(view: View) {
-        val holder = view.tag as GameViewHolder
-
-        val gameExists = DocumentFile.fromSingleUri(
-            YuzuApplication.appContext,
-            Uri.parse(holder.game.path)
-        )?.exists() == true
-        if (!gameExists) {
-            Toast.makeText(
-                YuzuApplication.appContext,
-                R.string.loader_error_file_not_found,
-                Toast.LENGTH_LONG
-            ).show()
-
-            ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
-            return
-        }
-
-        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-        preferences.edit()
-            .putLong(
-                holder.game.keyLastPlayedTime,
-                System.currentTimeMillis()
-            )
-            .apply()
-
-        val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
-            action = Intent.ACTION_VIEW
-            data = Uri.parse(holder.game.path)
-        }
-
-        activity.lifecycleScope.launch {
-            withContext(Dispatchers.IO) {
-                val layerDrawable = ResourcesCompat.getDrawable(
-                    YuzuApplication.appContext.resources,
-                    R.drawable.shortcut,
-                    null
-                ) as LayerDrawable
-                layerDrawable.setDrawableByLayerId(
-                    R.id.shortcut_foreground,
-                    GameIconUtils.getGameIcon(activity, holder.game)
-                        .toDrawable(YuzuApplication.appContext.resources)
-                )
-                val inset = YuzuApplication.appContext.resources
-                    .getDimensionPixelSize(R.dimen.icon_inset)
-                layerDrawable.setLayerInset(1, inset, inset, inset, inset)
-                val shortcut =
-                    ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
-                        .setShortLabel(holder.game.title)
-                        .setIcon(
-                            IconCompat.createWithAdaptiveBitmap(
-                                layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
-                            )
-                        )
-                        .setIntent(openIntent)
-                        .build()
-                ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
-            }
-        }
-
-        val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
-        view.findNavController().navigate(action)
-    }
-
-    override fun onLongClick(view: View): Boolean {
-        val holder = view.tag as GameViewHolder
-        val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
-        view.findNavController().navigate(action)
-        return true
+        CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return GameViewHolder(it) }
     }
 
     inner class GameViewHolder(val binding: CardGameBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var game: Game
-
-        init {
-            binding.cardGame.tag = this
-        }
-
-        fun bind(game: Game) {
-            this.game = game
-
+        AbstractViewHolder<Game>(binding) {
+        override fun bind(model: Game) {
             binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
-            GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
+            GameIconUtils.loadGameIcon(model, binding.imageGameScreen)
 
-            binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
+            binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
 
             binding.textGameTitle.postDelayed(
                 {
@@ -160,16 +59,79 @@ class GameAdapter(private val activity: AppCompatActivity) :
                 },
                 3000
             )
-        }
-    }
 
-    private class DiffCallback : DiffUtil.ItemCallback<Game>() {
-        override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
-            return oldItem == newItem
+            binding.cardGame.setOnClickListener { onClick(model) }
+            binding.cardGame.setOnLongClickListener { onLongClick(model) }
         }
 
-        override fun areContentsTheSame(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)
+        }
+
+        fun onLongClick(game: Game): Boolean {
+            val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game)
+            binding.root.findNavController().navigate(action)
+            return true
         }
     }
 }

From ad0066a6b651ab47b7b7e916a9f716ef77fe2e04 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 12:50:32 -0500
Subject: [PATCH 10/33] android: Create generic list adapter for basic lists

Simplifies basic setup for lists
---
 .../yuzu_emu/adapters/AbstractListAdapter.kt  | 98 +++++++++++++++++++
 1 file changed, 98 insertions(+)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
new file mode 100644
index 000000000..3dfee3d0c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractListAdapter.kt
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.annotation.SuppressLint
+import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+
+/**
+ * Generic list class meant to take care of basic lists
+ * @param currentList The list to show initially
+ */
+abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
+    open var currentList: List<Model>
+) : RecyclerView.Adapter<Holder>() {
+    override fun onBindViewHolder(holder: Holder, position: Int) =
+        holder.bind(currentList[position])
+
+    override fun getItemCount(): Int = currentList.size
+
+    /**
+     * Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter
+     * is passed in for position, [item] is added to the end of the list. Invokes [callback] last.
+     * @param item The item to add to the list
+     * @param position Index where [item] will be added
+     * @param callback Lambda that's called at the end of the list changes and has the added list
+     * position passed in as a parameter
+     */
+    open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) {
+        val newList = currentList.toMutableList()
+        val positionToUpdate: Int
+        if (position == -1) {
+            newList.add(item)
+            currentList = newList
+            positionToUpdate = currentList.size - 1
+        } else {
+            newList.add(position, item)
+            currentList = newList
+            positionToUpdate = position
+        }
+        onItemAdded(positionToUpdate, callback)
+    }
+
+    protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) {
+        notifyItemInserted(position)
+        callback?.invoke(position)
+    }
+
+    /**
+     * Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter
+     * of the change. Invokes [callback] last.
+     * @param item New list item
+     * @param position Index where [item] will replace the existing list item
+     * @param callback Lambda that's called at the end of the list changes and has the changed list
+     * position passed in as a parameter
+     */
+    fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) {
+        val newList = currentList.toMutableList()
+        newList[position] = item
+        currentList = newList
+        onItemChanged(position, callback)
+    }
+
+    protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) {
+        notifyItemChanged(position)
+        callback?.invoke(position)
+    }
+
+    /**
+     * Removes the list item at [position] in [currentList] and notifies the underlying adapter
+     * of the change. Invokes [callback] last.
+     * @param position Index where the list item will be removed
+     * @param callback Lambda that's called at the end of the list changes and has the removed list
+     * position passed in as a parameter
+     */
+    fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
+        val newList = currentList.toMutableList()
+        newList.removeAt(position)
+        currentList = newList
+        onItemRemoved(position, callback)
+    }
+
+    protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) {
+        notifyItemRemoved(position)
+        callback?.invoke(position)
+    }
+
+    /**
+     * Replaces [currentList] with [newList] and notifies the underlying adapter of the change.
+     * @param newList The new list to replace [currentList]
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    open fun replaceList(newList: List<Model>) {
+        currentList = newList
+        notifyDataSetChanged()
+    }
+}

From 9130366a580aa1129a596bf6ee3ab4432d219b6d Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 12:51:14 -0500
Subject: [PATCH 11/33] android: Refactor recycler view adapters to use
 AbstractListAdapter

---
 .../yuzu/yuzu_emu/adapters/AppletAdapter.kt   | 88 ++++++++-----------
 .../adapters/CabinetLauncherDialogAdapter.kt  | 57 +++++-------
 .../adapters/GamePropertiesAdapter.kt         | 28 ++----
 .../yuzu_emu/adapters/HomeSettingAdapter.kt   | 72 ++++++---------
 .../yuzu_emu/adapters/InstallableAdapter.kt   | 36 +++-----
 .../yuzu/yuzu_emu/adapters/LicenseAdapter.kt  | 50 ++++-------
 .../yuzu/yuzu_emu/adapters/SetupAdapter.kt    | 45 ++++------
 7 files changed, 140 insertions(+), 236 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
index 4a05c5be9..41d7f72b8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt
@@ -4,13 +4,11 @@
 package org.yuzu.yuzu_emu.adapters
 
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import android.widget.Toast
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.FragmentActivity
 import androidx.navigation.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
@@ -19,72 +17,58 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.model.Applet
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
-    RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
-    View.OnClickListener {
-
+class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
+    AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): AppletAdapter.AppletViewHolder {
         CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@AppletAdapter) }
             .also { return AppletViewHolder(it) }
     }
 
-    override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
-        holder.bind(applets[position])
-
-    override fun getItemCount(): Int = applets.size
-
-    override fun onClick(view: View) {
-        val applet = (view.tag as AppletViewHolder).applet
-        val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
-        if (appletPath.isEmpty()) {
-            Toast.makeText(
-                YuzuApplication.appContext,
-                R.string.applets_error_applet,
-                Toast.LENGTH_SHORT
-            ).show()
-            return
-        }
-
-        if (applet.appletInfo == AppletInfo.Cabinet) {
-            view.findNavController()
-                .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
-            return
-        }
-
-        NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
-        val appletGame = Game(
-            title = YuzuApplication.appContext.getString(applet.titleId),
-            path = appletPath
-        )
-        val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
-        view.findNavController().navigate(action)
-    }
-
     inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var applet: Applet
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(applet: Applet) {
-            this.applet = applet
-
-            binding.title.setText(applet.titleId)
-            binding.description.setText(applet.descriptionId)
+        AbstractViewHolder<Applet>(binding) {
+        override fun bind(model: Applet) {
+            binding.title.setText(model.titleId)
+            binding.description.setText(model.descriptionId)
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
-                    applet.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                 )
             )
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        fun onClick(applet: Applet) {
+            val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
+            if (appletPath.isEmpty()) {
+                Toast.makeText(
+                    binding.root.context,
+                    R.string.applets_error_applet,
+                    Toast.LENGTH_SHORT
+                ).show()
+                return
+            }
+
+            if (applet.appletInfo == AppletInfo.Cabinet) {
+                binding.root.findNavController()
+                    .navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
+                return
+            }
+
+            NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
+            val appletGame = Game(
+                title = YuzuApplication.appContext.getString(applet.titleId),
+                path = appletPath
+            )
+            val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+            binding.root.findNavController().navigate(action)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
index e7b7c0f2f..a56137148 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/CabinetLauncherDialogAdapter.kt
@@ -4,12 +4,10 @@
 package org.yuzu.yuzu_emu.adapters
 
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup
 import androidx.core.content.res.ResourcesCompat
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
@@ -19,54 +17,43 @@ import org.yuzu.yuzu_emu.model.CabinetMode
 import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
 import org.yuzu.yuzu_emu.model.AppletInfo
 import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class CabinetLauncherDialogAdapter(val fragment: Fragment) :
-    RecyclerView.Adapter<CabinetModeViewHolder>(),
-    View.OnClickListener {
-    private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
+    AbstractListAdapter<CabinetMode, CabinetModeViewHolder>(
+        CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList()
+    ) {
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
         DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-            .apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
             .also { return CabinetModeViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = cabinetModes.size
-
-    override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
-        holder.bind(cabinetModes[position])
-
-    override fun onClick(view: View) {
-        val mode = (view.tag as CabinetModeViewHolder).cabinetMode
-        val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
-        NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
-        NativeLibrary.setCabinetMode(mode.id)
-        val appletGame = Game(
-            title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
-            path = appletPath
-        )
-        val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
-        fragment.findNavController().navigate(action)
-    }
-
     inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var cabinetMode: CabinetMode
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(cabinetMode: CabinetMode) {
-            this.cabinetMode = cabinetMode
+        AbstractViewHolder<CabinetMode>(binding) {
+        override fun bind(model: CabinetMode) {
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     binding.icon.context.resources,
-                    cabinetMode.iconId,
+                    model.iconId,
                     binding.icon.context.theme
                 )
             )
-            binding.title.setText(cabinetMode.titleId)
+            binding.title.setText(model.titleId)
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        private fun onClick(mode: CabinetMode) {
+            val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
+            NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
+            NativeLibrary.setCabinetMode(mode.id)
+            val appletGame = Game(
+                title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
+                path = appletPath
+            )
+            val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
+            fragment.findNavController().navigate(action)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
index 95841d786..0046d5314 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt
@@ -12,23 +12,22 @@ import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
 import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
 import org.yuzu.yuzu_emu.model.GameProperty
 import org.yuzu.yuzu_emu.model.InstallableProperty
 import org.yuzu.yuzu_emu.model.SubmenuProperty
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class GamePropertiesAdapter(
     private val viewLifecycle: LifecycleOwner,
     private var properties: List<GameProperty>
-) :
-    RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
+) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
-    ): GamePropertyViewHolder {
+    ): AbstractViewHolder<GameProperty> {
         val inflater = LayoutInflater.from(parent.context)
         return when (viewType) {
             PropertyType.Submenu.ordinal -> {
@@ -51,11 +50,6 @@ class GamePropertiesAdapter(
         }
     }
 
-    override fun getItemCount(): Int = properties.size
-
-    override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
-        holder.bind(properties[position])
-
     override fun getItemViewType(position: Int): Int {
         return when (properties[position]) {
             is SubmenuProperty -> PropertyType.Submenu.ordinal
@@ -63,14 +57,10 @@ class GamePropertiesAdapter(
         }
     }
 
-    sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-        abstract fun bind(property: GameProperty)
-    }
-
     inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
-        GamePropertyViewHolder(binding.root) {
-        override fun bind(property: GameProperty) {
-            val submenuProperty = property as SubmenuProperty
+        AbstractViewHolder<GameProperty>(binding) {
+        override fun bind(model: GameProperty) {
+            val submenuProperty = model as SubmenuProperty
 
             binding.root.setOnClickListener {
                 submenuProperty.action.invoke()
@@ -108,9 +98,9 @@ class GamePropertiesAdapter(
     }
 
     inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
-        GamePropertyViewHolder(binding.root) {
-        override fun bind(property: GameProperty) {
-            val installableProperty = property as InstallableProperty
+        AbstractViewHolder<GameProperty>(binding) {
+        override fun bind(model: GameProperty) {
+            val installableProperty = model as InstallableProperty
 
             binding.title.setText(installableProperty.titleId)
             binding.description.setText(installableProperty.descriptionId)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 58ce343f4..b512845d5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -14,69 +14,37 @@ import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.HomeSetting
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class HomeSettingAdapter(
     private val activity: AppCompatActivity,
     private val viewLifecycle: LifecycleOwner,
-    var options: List<HomeSetting>
-) :
-    RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
-    View.OnClickListener {
+    options: List<HomeSetting>
+) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
-        val binding =
-            CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        binding.root.setOnClickListener(this)
-        return HomeOptionViewHolder(binding)
-    }
-
-    override fun getItemCount(): Int {
-        return options.size
-    }
-
-    override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
-        holder.bind(options[position])
-    }
-
-    override fun onClick(view: View) {
-        val holder = view.tag as HomeOptionViewHolder
-        if (holder.option.isEnabled.invoke()) {
-            holder.option.onClick.invoke()
-        } else {
-            MessageDialogFragment.newInstance(
-                activity,
-                titleId = holder.option.disabledTitleId,
-                descriptionId = holder.option.disabledMessageId
-            ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
-        }
+        CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return HomeOptionViewHolder(it) }
     }
 
     inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var option: HomeSetting
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(option: HomeSetting) {
-            this.option = option
-            binding.optionTitle.text = activity.resources.getString(option.titleId)
-            binding.optionDescription.text = activity.resources.getString(option.descriptionId)
+        AbstractViewHolder<HomeSetting>(binding) {
+        override fun bind(model: HomeSetting) {
+            binding.optionTitle.text = activity.resources.getString(model.titleId)
+            binding.optionDescription.text = activity.resources.getString(model.descriptionId)
             binding.optionIcon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
-                    option.iconId,
+                    model.iconId,
                     activity.theme
                 )
             )
 
-            when (option.titleId) {
+            when (model.titleId) {
                 R.string.get_early_access ->
                     binding.optionLayout.background =
                         ContextCompat.getDrawable(
@@ -85,7 +53,7 @@ class HomeSettingAdapter(
                         )
             }
 
-            if (!option.isEnabled.invoke()) {
+            if (!model.isEnabled.invoke()) {
                 binding.optionTitle.alpha = 0.5f
                 binding.optionDescription.alpha = 0.5f
                 binding.optionIcon.alpha = 0.5f
@@ -93,7 +61,7 @@ class HomeSettingAdapter(
 
             viewLifecycle.lifecycleScope.launch {
                 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    option.details.collect { updateOptionDetails(it) }
+                    model.details.collect { updateOptionDetails(it) }
                 }
             }
             binding.optionDetail.postDelayed(
@@ -103,6 +71,20 @@ class HomeSettingAdapter(
                 },
                 3000
             )
+
+            binding.root.setOnClickListener { onClick(model) }
+        }
+
+        private fun onClick(model: HomeSetting) {
+            if (model.isEnabled.invoke()) {
+                model.onClick.invoke()
+            } else {
+                MessageDialogFragment.newInstance(
+                    activity,
+                    titleId = model.disabledTitleId,
+                    descriptionId = model.disabledMessageId
+                ).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
+            }
         }
 
         private fun updateOptionDetails(detailString: String) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
index e960fbaab..4218c4e52 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt
@@ -6,43 +6,33 @@ package org.yuzu.yuzu_emu.adapters
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
 import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
 import org.yuzu.yuzu_emu.model.Installable
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class InstallableAdapter(private val installables: List<Installable>) :
-    RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
+class InstallableAdapter(installables: List<Installable>) :
+    AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): InstallableAdapter.InstallableViewHolder {
-        val binding =
-            CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        return InstallableViewHolder(binding)
+        CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return InstallableViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = installables.size
-
-    override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
-        holder.bind(installables[position])
-
     inner class InstallableViewHolder(val binding: CardInstallableBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        lateinit var installable: Installable
+        AbstractViewHolder<Installable>(binding) {
+        override fun bind(model: Installable) {
+            binding.title.setText(model.titleId)
+            binding.description.setText(model.descriptionId)
 
-        fun bind(installable: Installable) {
-            this.installable = installable
-
-            binding.title.setText(installable.titleId)
-            binding.description.setText(installable.descriptionId)
-
-            if (installable.install != null) {
+            if (model.install != null) {
                 binding.buttonInstall.visibility = View.VISIBLE
-                binding.buttonInstall.setOnClickListener { installable.install.invoke() }
+                binding.buttonInstall.setOnClickListener { model.install.invoke() }
             }
-            if (installable.export != null) {
+            if (model.export != null) {
                 binding.buttonExport.visibility = View.VISIBLE
-                binding.buttonExport.setOnClickListener { installable.export.invoke() }
+                binding.buttonExport.setOnClickListener { model.export.invoke() }
             }
         }
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
index bc6ff1364..38bb1f96f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt
@@ -7,49 +7,33 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
 import org.yuzu.yuzu_emu.model.License
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
-    RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
-    View.OnClickListener {
+class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
+    AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
-        val binding =
-            ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        binding.root.setOnClickListener(this)
-        return LicenseViewHolder(binding)
+        ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return LicenseViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = licenses.size
+    inner class LicenseViewHolder(val binding: ListItemSettingBinding) :
+        AbstractViewHolder<License>(binding) {
+        override fun bind(model: License) {
+            binding.apply {
+                textSettingName.text = root.context.getString(model.titleId)
+                textSettingDescription.text = root.context.getString(model.descriptionId)
+                textSettingValue.visibility = View.GONE
 
-    override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
-        holder.bind(licenses[position])
-    }
-
-    override fun onClick(view: View) {
-        val license = (view.tag as LicenseViewHolder).license
-        LicenseBottomSheetDialogFragment.newInstance(license)
-            .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
-    }
-
-    inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
-        lateinit var license: License
-
-        init {
-            itemView.tag = this
+                root.setOnClickListener { onClick(model) }
+            }
         }
 
-        fun bind(license: License) {
-            this.license = license
-
-            val context = YuzuApplication.appContext
-            binding.textSettingName.text = context.getString(license.titleId)
-            binding.textSettingDescription.text = context.getString(license.descriptionId)
-            binding.textSettingValue.visibility = View.GONE
+        private fun onClick(license: License) {
+            LicenseBottomSheetDialogFragment.newInstance(license)
+                .show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 6b46d359e..02118e1a8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -10,7 +10,6 @@ import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.res.ResourcesCompat
 import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.button.MaterialButton
 import org.yuzu.yuzu_emu.databinding.PageSetupBinding
 import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -18,31 +17,19 @@ import org.yuzu.yuzu_emu.model.SetupCallback
 import org.yuzu.yuzu_emu.model.SetupPage
 import org.yuzu.yuzu_emu.model.StepState
 import org.yuzu.yuzu_emu.utils.ViewUtils
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
-class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
-    RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
+class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
+    AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
-        val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        return SetupPageViewHolder(binding)
+        PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return SetupPageViewHolder(it) }
     }
 
-    override fun getItemCount(): Int = pages.size
-
-    override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
-        holder.bind(pages[position])
-
     inner class SetupPageViewHolder(val binding: PageSetupBinding) :
-        RecyclerView.ViewHolder(binding.root), SetupCallback {
-        lateinit var page: SetupPage
-
-        init {
-            itemView.tag = this
-        }
-
-        fun bind(page: SetupPage) {
-            this.page = page
-
-            if (page.stepCompleted.invoke() == StepState.COMPLETE) {
+        AbstractViewHolder<SetupPage>(binding), SetupCallback {
+        override fun bind(model: SetupPage) {
+            if (model.stepCompleted.invoke() == StepState.COMPLETE) {
                 binding.buttonAction.visibility = View.INVISIBLE
                 binding.textConfirmation.visibility = View.VISIBLE
             }
@@ -50,31 +37,31 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
             binding.icon.setImageDrawable(
                 ResourcesCompat.getDrawable(
                     activity.resources,
-                    page.iconId,
+                    model.iconId,
                     activity.theme
                 )
             )
-            binding.textTitle.text = activity.resources.getString(page.titleId)
+            binding.textTitle.text = activity.resources.getString(model.titleId)
             binding.textDescription.text =
-                Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
+                Html.fromHtml(activity.resources.getString(model.descriptionId), 0)
 
             binding.buttonAction.apply {
-                text = activity.resources.getString(page.buttonTextId)
-                if (page.buttonIconId != 0) {
+                text = activity.resources.getString(model.buttonTextId)
+                if (model.buttonIconId != 0) {
                     icon = ResourcesCompat.getDrawable(
                         activity.resources,
-                        page.buttonIconId,
+                        model.buttonIconId,
                         activity.theme
                     )
                 }
                 iconGravity =
-                    if (page.leftAlignedIcon) {
+                    if (model.leftAlignedIcon) {
                         MaterialButton.ICON_GRAVITY_START
                     } else {
                         MaterialButton.ICON_GRAVITY_END
                     }
                 setOnClickListener {
-                    page.buttonAction.invoke(this@SetupPageViewHolder)
+                    model.buttonAction.invoke(this@SetupPageViewHolder)
                 }
             }
         }

From b17db2b4627acc1f9d0e9862ccfa802cf959f1e1 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 13:04:06 -0500
Subject: [PATCH 12/33] android: Create generic single selection list adapter

---
 .../adapters/AbstractSingleSelectionList.kt   | 105 ++++++++++++++++++
 .../org/yuzu/yuzu_emu/model/SelectableItem.kt |   9 ++
 2 files changed, 114 insertions(+)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
new file mode 100644
index 000000000..52163f9d7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AbstractSingleSelectionList.kt
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import org.yuzu.yuzu_emu.model.SelectableItem
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
+
+/**
+ * Generic list class meant to take care of single selection UI updates
+ * @param currentList The list to show initially
+ * @param defaultSelection The default selection to use if no list items are selected by
+ * [SelectableItem.selected] or if the currently selected item is removed from the list
+ */
+abstract class AbstractSingleSelectionList<
+    Model : SelectableItem,
+    Holder : AbstractViewHolder<Model>
+    >(
+    final override var currentList: List<Model>,
+    private val defaultSelection: DefaultSelection = DefaultSelection.Start
+) : AbstractListAdapter<Model, Holder>(currentList) {
+    var selectedItem = getDefaultSelection()
+
+    init {
+        findSelectedItem()
+    }
+
+    /**
+     * Changes the selection state of the [SelectableItem] that was selected and the previously selected
+     * item and notifies the underlying adapter of the change for those items. Invokes [callback] last.
+     * Does nothing if [position] is the same as the currently selected item.
+     * @param position Index of the item that was selected
+     * @param callback Lambda that's called at the end of the list changes and has the selected list
+     * position passed in as a parameter
+     */
+    fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
+        if (position == selectedItem) {
+            return
+        }
+
+        val previouslySelectedItem = selectedItem
+        selectedItem = position
+        if (currentList.indices.contains(selectedItem)) {
+            currentList[selectedItem].onSelectionStateChanged(true)
+        }
+        if (currentList.indices.contains(previouslySelectedItem)) {
+            currentList[previouslySelectedItem].onSelectionStateChanged(false)
+        }
+        onItemChanged(previouslySelectedItem)
+        onItemChanged(selectedItem)
+        callback?.invoke(position)
+    }
+
+    /**
+     * Removes a given item from the list and notifies the underlying adapter of the change. If the
+     * currently selected item was the item that was removed, the item at the position provided
+     * by [defaultSelection] will be made the new selection. Invokes [callback] last.
+     * @param position Index of the item that was removed
+     * @param callback Lambda that's called at the end of the list changes and has the removed and
+     * selected list positions passed in as parameters
+     */
+    fun removeSelectableItem(
+        position: Int,
+        callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)?
+    ) {
+        removeItem(position)
+        if (position == selectedItem) {
+            selectedItem = getDefaultSelection()
+            currentList[selectedItem].onSelectionStateChanged(true)
+            onItemChanged(selectedItem)
+        } else if (position < selectedItem) {
+            selectedItem--
+        }
+        callback?.invoke(position, selectedItem)
+    }
+
+    override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) {
+        super.addItem(item, position, callback)
+        if (position <= selectedItem && position != -1) {
+            selectedItem++
+        }
+    }
+
+    override fun replaceList(newList: List<Model>) {
+        super.replaceList(newList)
+        findSelectedItem()
+    }
+
+    private fun findSelectedItem() {
+        for (i in currentList.indices) {
+            if (currentList[i].selected) {
+                selectedItem = i
+                break
+            }
+        }
+    }
+
+    private fun getDefaultSelection(): Int =
+        when (defaultSelection) {
+            DefaultSelection.Start -> currentList.indices.first
+            DefaultSelection.End -> currentList.indices.last
+        }
+
+    enum class DefaultSelection { Start, End }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
new file mode 100644
index 000000000..11c22d323
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SelectableItem.kt
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+interface SelectableItem {
+    var selected: Boolean
+    fun onSelectionStateChanged(selected: Boolean)
+}

From 93239f191a17179599ccadea29b9f4adb5a4dc60 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 13:05:50 -0500
Subject: [PATCH 13/33] android: Refactor DriverAdapter to use
 AbstractSingleSelectionList

---
 .../yuzu/yuzu_emu/adapters/DriverAdapter.kt   | 96 +++++--------------
 1 file changed, 23 insertions(+), 73 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
index d290a656c..ca353cea7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -7,65 +7,34 @@ import android.text.TextUtils
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.recyclerview.widget.AsyncDifferConfig
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.ListAdapter
-import androidx.recyclerview.widget.RecyclerView
-import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
+import org.yuzu.yuzu_emu.model.Driver
 import org.yuzu.yuzu_emu.model.DriverViewModel
-import org.yuzu.yuzu_emu.utils.GpuDriverHelper
-import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class DriverAdapter(private val driverViewModel: DriverViewModel) :
-    ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
-        AsyncDifferConfig.Builder(DiffCallback()).build()
+    AbstractSingleSelectionList<Driver, DriverAdapter.DriverViewHolder>(
+        driverViewModel.driverList.value
     ) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
-        val binding =
-            CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
-        return DriverViewHolder(binding)
-    }
-
-    override fun getItemCount(): Int = currentList.size
-
-    override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
-        holder.bind(currentList[position])
-
-    private fun onSelectDriver(position: Int) {
-        driverViewModel.setSelectedDriverIndex(position)
-        notifyItemChanged(driverViewModel.previouslySelectedDriver)
-        notifyItemChanged(driverViewModel.selectedDriver)
-    }
-
-    private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
-        if (driverViewModel.selectedDriver > position) {
-            driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
-        }
-        if (GpuDriverHelper.customDriverSettingData == driverData.second) {
-            driverViewModel.setSelectedDriverIndex(0)
-        }
-        driverViewModel.driversToDelete.add(driverData.first)
-        driverViewModel.removeDriver(driverData)
-        notifyItemRemoved(position)
-        notifyItemChanged(driverViewModel.selectedDriver)
+        CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+            .also { return DriverViewHolder(it) }
     }
 
     inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
-        RecyclerView.ViewHolder(binding.root) {
-        private lateinit var driverData: Pair<String, GpuDriverMetadata>
-
-        fun bind(driverData: Pair<String, GpuDriverMetadata>) {
-            this.driverData = driverData
-            val driver = driverData.second
-
+        AbstractViewHolder<Driver>(binding) {
+        override fun bind(model: Driver) {
             binding.apply {
-                radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
+                radioButton.isChecked = model.selected
                 root.setOnClickListener {
-                    onSelectDriver(bindingAdapterPosition)
+                    selectItem(bindingAdapterPosition) { driverViewModel.onDriverSelected(it) }
                 }
                 buttonDelete.setOnClickListener {
-                    onDeleteDriver(driverData, bindingAdapterPosition)
+                    removeSelectableItem(
+                        bindingAdapterPosition
+                    ) { removedPosition: Int, selectedPosition: Int ->
+                        driverViewModel.onDriverRemoved(removedPosition, selectedPosition)
+                    }
                 }
 
                 // Delay marquee by 3s
@@ -80,38 +49,19 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
                     },
                     3000
                 )
-                if (driver.name == null) {
-                    title.setText(R.string.system_gpu_driver)
-                    description.text = ""
-                    version.text = ""
-                    version.visibility = View.GONE
-                    description.visibility = View.GONE
-                    buttonDelete.visibility = View.GONE
-                } else {
-                    title.text = driver.name
-                    version.text = driver.version
-                    description.text = driver.description
+                title.text = model.title
+                version.text = model.version
+                description.text = model.description
+                if (model.description.isNotEmpty()) {
                     version.visibility = View.VISIBLE
                     description.visibility = View.VISIBLE
                     buttonDelete.visibility = View.VISIBLE
+                } else {
+                    version.visibility = View.GONE
+                    description.visibility = View.GONE
+                    buttonDelete.visibility = View.GONE
                 }
             }
         }
     }
-
-    private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
-        override fun areItemsTheSame(
-            oldItem: Pair<String, GpuDriverMetadata>,
-            newItem: Pair<String, GpuDriverMetadata>
-        ): Boolean {
-            return oldItem.first == newItem.first
-        }
-
-        override fun areContentsTheSame(
-            oldItem: Pair<String, GpuDriverMetadata>,
-            newItem: Pair<String, GpuDriverMetadata>
-        ): Boolean {
-            return oldItem.second == newItem.second
-        }
-    }
 }

From 6bfc3c530ce0c8ba3ac0a62609d1266aa8d67d35 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 13:09:06 -0500
Subject: [PATCH 14/33] android: Rework driver fragment

Applies settings upon selection and uses a new Driver model to represent the information in-view. Also switches from an async diff list to a plain one.
---
 .../fragments/DriverManagerFragment.kt        |  39 ++----
 .../java/org/yuzu/yuzu_emu/model/Driver.kt    |  27 ++++
 .../yuzu/yuzu_emu/model/DriverViewModel.kt    | 129 ++++++------------
 .../yuzu/yuzu_emu/utils/GpuDriverHelper.kt    |   3 -
 4 files changed, 86 insertions(+), 112 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index cc71254dc..82c966954 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -13,16 +13,16 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.lifecycleScope
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.GridLayoutManager
 import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.DriverAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.utils.FileUtil
@@ -85,25 +85,6 @@ class DriverManagerFragment : Fragment() {
             adapter = DriverAdapter(driverViewModel)
         }
 
-        viewLifecycleOwner.lifecycleScope.apply {
-            launch {
-                driverViewModel.driverList.collectLatest {
-                    (binding.listDrivers.adapter as DriverAdapter).submitList(it)
-                }
-            }
-            launch {
-                driverViewModel.newDriverInstalled.collect {
-                    if (_binding != null && it) {
-                        (binding.listDrivers.adapter as DriverAdapter).apply {
-                            notifyItemChanged(driverViewModel.previouslySelectedDriver)
-                            notifyItemChanged(driverViewModel.selectedDriver)
-                            driverViewModel.setNewDriverInstalled(false)
-                        }
-                    }
-                }
-            }
-        }
-
         setInsets()
     }
 
@@ -177,12 +158,20 @@ class DriverManagerFragment : Fragment() {
 
                 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
                 val driverInList =
-                    driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+                    driverViewModel.driverData.firstOrNull { it.second == driverData }
                 if (driverInList != null) {
                     return@newInstance getString(R.string.driver_already_installed)
                 } else {
-                    driverViewModel.addDriver(Pair(driverPath, driverData))
-                    driverViewModel.setNewDriverInstalled(true)
+                    driverViewModel.onDriverAdded(Pair(driverPath, driverData))
+                    withContext(Dispatchers.Main) {
+                        if (_binding != null) {
+                            val adapter = binding.listDrivers.adapter as DriverAdapter
+                            adapter.addItem(driverData.toDriver())
+                            adapter.selectItem(adapter.currentList.indices.last)
+                            binding.listDrivers
+                                .smoothScrollToPosition(adapter.currentList.indices.last)
+                        }
+                    }
                 }
                 return@newInstance Any()
             }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
new file mode 100644
index 000000000..de342212a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+
+data class Driver(
+    override var selected: Boolean,
+    val title: String,
+    val version: String = "",
+    val description: String = ""
+) : SelectableItem {
+    override fun onSelectionStateChanged(selected: Boolean) {
+        this.selected = selected
+    }
+
+    companion object {
+        fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver =
+            Driver(
+                selected,
+                this.name ?: "",
+                this.version ?: "",
+                this.description ?: ""
+            )
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
index 76accf8f3..a1fee48cc 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -17,11 +17,10 @@ import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
 import org.yuzu.yuzu_emu.utils.GpuDriverHelper
 import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
 import org.yuzu.yuzu_emu.utils.NativeConfig
-import java.io.BufferedOutputStream
 import java.io.File
 
 class DriverViewModel : ViewModel() {
@@ -38,97 +37,74 @@ class DriverViewModel : ViewModel() {
             !loading && ready && !deleting
         }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false)
 
-    private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers())
-    val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
+    var driverData = GpuDriverHelper.getDrivers()
 
-    var previouslySelectedDriver = 0
-    var selectedDriver = -1
+    private val _driverList = MutableStateFlow(emptyList<Driver>())
+    val driverList: StateFlow<List<Driver>> get() = _driverList
 
     // Used for showing which driver is currently installed within the driver manager card
     private val _selectedDriverTitle = MutableStateFlow("")
     val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
 
-    private val _newDriverInstalled = MutableStateFlow(false)
-    val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
-
-    val driversToDelete = mutableListOf<String>()
+    private val driversToDelete = mutableListOf<String>()
 
     init {
-        val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData
-        findSelectedDriver(currentDriverMetadata)
-
-        // If a user had installed a driver before the manager was implemented, this zips
-        // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
-        // be indexed and exported as expected.
-        if (selectedDriver == -1) {
-            val driverToSave =
-                File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
-            driverToSave.createNewFile()
-            FileUtil.zipFromInternalStorage(
-                File(GpuDriverHelper.driverInstallationPath!!),
-                GpuDriverHelper.driverInstallationPath!!,
-                BufferedOutputStream(driverToSave.outputStream())
-            )
-            _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
-            setSelectedDriverIndex(_driverList.value.size - 1)
-        }
-
-        // 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)
-        }
+        updateDriverList()
         updateDriverNameForGame(null)
     }
 
-    fun setSelectedDriverIndex(value: Int) {
-        if (selectedDriver != -1) {
-            previouslySelectedDriver = selectedDriver
+    fun reloadDriverData() {
+        _areDriversLoading.value = true
+        driverData = GpuDriverHelper.getDrivers()
+        updateDriverList()
+        _areDriversLoading.value = false
+    }
+
+    private 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))
         }
-        selectedDriver = value
-    }
-
-    fun setNewDriverInstalled(value: Boolean) {
-        _newDriverInstalled.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)
-        } else {
-            setSelectedDriverIndex(driverIndex)
-        }
-    }
-
-    fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
-        _driverList.value.remove(driverData)
+        _driverList.value = newDriverList
     }
 
     fun onOpenDriverManager(game: Game?) {
         if (game != null) {
             SettingsFile.loadCustomConfig(game)
         }
+        updateDriverList()
+    }
 
-        val driverPath = StringSetting.DRIVER_PATH.getString()
-        if (driverPath.isEmpty()) {
-            setSelectedDriverIndex(0)
+    fun onDriverSelected(position: Int) {
+        if (position == 0) {
+            StringSetting.DRIVER_PATH.setString("")
         } else {
-            findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath)))
+            StringSetting.DRIVER_PATH.setString(driverData[position - 1].first)
         }
     }
 
+    fun onDriverRemoved(removedPosition: Int, selectedPosition: Int) {
+        driversToDelete.add(driverData[removedPosition - 1].first)
+        driverData.removeAt(removedPosition - 1)
+        onDriverSelected(selectedPosition)
+    }
+
+    fun onDriverAdded(driver: Pair<String, GpuDriverMetadata>) {
+        if (driversToDelete.contains(driver.first)) {
+            driversToDelete.remove(driver.first)
+        }
+        driverData.add(driver)
+        onDriverSelected(driverData.size)
+    }
+
     fun onCloseDriverManager(game: Game?) {
         _isDeletingDrivers.value = true
-        StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first)
         updateDriverNameForGame(game)
         if (game == null) {
             NativeConfig.saveGlobalConfig()
@@ -181,20 +157,6 @@ class DriverViewModel : ViewModel() {
         }
     }
 
-    private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) {
-        if (driverList.value.size == 1) {
-            setSelectedDriverIndex(0)
-            return
-        }
-
-        driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> ->
-            if (driver.second == currentDriverMetadata) {
-                setSelectedDriverIndex(i)
-                return
-            }
-        }
-    }
-
     fun updateDriverNameForGame(game: Game?) {
         if (!GpuDriverHelper.supportsCustomDriverLoading()) {
             return
@@ -217,7 +179,6 @@ class DriverViewModel : ViewModel() {
 
     private fun setDriverReady() {
         _isDriverReady.value = true
-        _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
-            ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+        updateName()
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 685272288..a8f9dcc34 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -62,9 +62,6 @@ object GpuDriverHelper {
                 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
                 ?.distinct()
                 ?.toMutableList() ?: mutableListOf()
-
-        // TODO: Get system driver information
-        drivers.add(0, Pair("", GpuDriverMetadata()))
         return drivers
     }
 

From 9e974d4c7e44b1067a5c71c56b172eb15c75e43f Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 13:09:21 -0500
Subject: [PATCH 15/33] android: Reload driver data on importing user data

---
 .../src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt    | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 622ae996e..644289e25 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -41,6 +41,7 @@ import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
 import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
 import org.yuzu.yuzu_emu.model.AddonViewModel
+import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.GamesViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.model.TaskState
@@ -58,6 +59,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
     private val gamesViewModel: GamesViewModel by viewModels()
     private val taskViewModel: TaskViewModel by viewModels()
     private val addonViewModel: AddonViewModel by viewModels()
+    private val driverViewModel: DriverViewModel by viewModels()
 
     override var themeId: Int = 0
 
@@ -689,6 +691,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                 NativeLibrary.initializeSystem(true)
                 NativeConfig.initializeGlobalConfig()
                 gamesViewModel.reloadGames(false)
+                driverViewModel.reloadDriverData()
 
                 return@newInstance getString(R.string.user_data_import_success)
             }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)

From dac8c4ce4d41d12b812caa59bc7f040220caf652 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 15:34:14 -0500
Subject: [PATCH 16/33] android: Add button to use global driver value

---
 .../yuzu/yuzu_emu/adapters/DriverAdapter.kt   |  7 +++-
 .../fragments/DriverManagerFragment.kt        | 39 +++++++++++++++++++
 .../yuzu/yuzu_emu/model/DriverViewModel.kt    | 10 ++++-
 .../src/main/res/menu/menu_driver_manager.xml | 11 ++++++
 4 files changed, 65 insertions(+), 2 deletions(-)
 create mode 100644 src/android/app/src/main/res/menu/menu_driver_manager.xml

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
index ca353cea7..d6f17cf29 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 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.viewholder.AbstractViewHolder
@@ -27,13 +28,17 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
             binding.apply {
                 radioButton.isChecked = model.selected
                 root.setOnClickListener {
-                    selectItem(bindingAdapterPosition) { driverViewModel.onDriverSelected(it) }
+                    selectItem(bindingAdapterPosition) {
+                        driverViewModel.onDriverSelected(it)
+                        driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+                    }
                 }
                 buttonDelete.setOnClickListener {
                     removeSelectableItem(
                         bindingAdapterPosition
                     ) { removedPosition: Int, selectedPosition: Int ->
                         driverViewModel.onDriverRemoved(removedPosition, selectedPosition)
+                        driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
                     }
                 }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index 82c966954..c01fdff25 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -3,6 +3,7 @@
 
 package org.yuzu.yuzu_emu.fragments
 
+import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -13,20 +14,26 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.GridLayoutManager
 import com.google.android.material.transition.MaterialSharedAxis
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.DriverAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.utils.FileUtil
 import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.NativeConfig
 import java.io.File
 import java.io.IOException
 
@@ -55,12 +62,43 @@ class DriverManagerFragment : Fragment() {
         return binding.root
     }
 
+    // This is using the correct scope, lint is just acting up
+    @SuppressLint("UnsafeRepeatOnLifecycleDetector")
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         homeViewModel.setNavigationVisibility(visible = false, animated = true)
         homeViewModel.setStatusBarShadeVisibility(visible = false)
 
         driverViewModel.onOpenDriverManager(args.game)
+        if (NativeConfig.isPerGameConfigLoaded()) {
+            binding.toolbarDrivers.inflateMenu(R.menu.menu_driver_manager)
+            driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
+            binding.toolbarDrivers.setOnMenuItemClickListener {
+                when (it.itemId) {
+                    R.id.menu_driver_clear -> {
+                        StringSetting.DRIVER_PATH.global = true
+                        driverViewModel.updateDriverList()
+                        (binding.listDrivers.adapter as DriverAdapter)
+                            .replaceList(driverViewModel.driverList.value)
+                        driverViewModel.showClearButton(false)
+                        true
+                    }
+
+                    else -> false
+                }
+            }
+
+            viewLifecycleOwner.lifecycleScope.apply {
+                launch {
+                    repeatOnLifecycle(Lifecycle.State.STARTED) {
+                        driverViewModel.showClearButton.collect {
+                            binding.toolbarDrivers.menu
+                                .findItem(R.id.menu_driver_clear).isVisible = it
+                        }
+                    }
+                }
+            }
+        }
 
         if (!driverViewModel.isInteractionAllowed.value) {
             DriversLoadingDialogFragment().show(
@@ -168,6 +206,7 @@ class DriverManagerFragment : Fragment() {
                             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)
                         }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
index a1fee48cc..15ae3a42b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -46,6 +47,9 @@ class DriverViewModel : ViewModel() {
     private val _selectedDriverTitle = MutableStateFlow("")
     val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
 
+    private val _showClearButton = MutableStateFlow(false)
+    val showClearButton = _showClearButton.asStateFlow()
+
     private val driversToDelete = mutableListOf<String>()
 
     init {
@@ -60,7 +64,7 @@ class DriverViewModel : ViewModel() {
         _areDriversLoading.value = false
     }
 
-    private fun updateDriverList() {
+    fun updateDriverList() {
         val selectedDriver = GpuDriverHelper.customDriverSettingData
         val newDriverList = mutableListOf(
             Driver(
@@ -81,6 +85,10 @@ class DriverViewModel : ViewModel() {
         updateDriverList()
     }
 
+    fun showClearButton(value: Boolean) {
+        _showClearButton.value = value
+    }
+
     fun onDriverSelected(position: Int) {
         if (position == 0) {
             StringSetting.DRIVER_PATH.setString("")
diff --git a/src/android/app/src/main/res/menu/menu_driver_manager.xml b/src/android/app/src/main/res/menu/menu_driver_manager.xml
new file mode 100644
index 000000000..dee5d57b6
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_driver_manager.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/menu_driver_clear"
+        android:icon="@drawable/ic_clear"
+        android:title="@string/clear"
+        app:showAsAction="always" />
+
+</menu>

From d3ba6b334bdec89834f511dff70217fc9adb943c Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 15:36:22 -0500
Subject: [PATCH 17/33] android: Fix added driver path

While this didn't break anything, the extra separator was unnecessary
---
 .../java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index c01fdff25..9dabb9c41 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -179,7 +179,7 @@ class DriverManagerFragment : Fragment() {
                 false
             ) {
                 val driverPath =
-                    "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+                    "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
                 val driverFile = File(driverPath)
 
                 // Ignore file exceptions when a user selects an invalid zip

From aae9eea53208fc0924c90ebb1272fcfaa3f23e0c Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 8 Jan 2024 00:49:00 -0500
Subject: [PATCH 18/33] fsp-srv: use program registry for SetCurrentProcess

---
 src/android/app/src/main/jni/native.cpp       |   6 +-
 src/core/CMakeLists.txt                       |   4 +
 src/core/core.cpp                             |   1 +
 src/core/file_sys/savedata_factory.cpp        |  17 +-
 src/core/file_sys/savedata_factory.h          |  10 +-
 src/core/hle/service/am/am.cpp                |   7 +-
 .../hle/service/filesystem/filesystem.cpp     | 235 ++++--------------
 src/core/hle/service/filesystem/filesystem.h  |  55 ++--
 src/core/hle/service/filesystem/fsp_srv.cpp   |  55 ++--
 src/core/hle/service/filesystem/fsp_srv.h     |   6 +
 .../service/filesystem/romfs_controller.cpp   |  37 +++
 .../hle/service/filesystem/romfs_controller.h |  31 +++
 .../filesystem/save_data_controller.cpp       |  99 ++++++++
 .../service/filesystem/save_data_controller.h |  35 +++
 .../loader/deconstructed_rom_directory.cpp    |  14 --
 src/core/loader/nca.cpp                       |   6 +-
 src/core/loader/nro.cpp                       |   6 +-
 src/core/loader/nsp.cpp                       |   3 +-
 src/core/loader/xci.cpp                       |   3 +-
 src/yuzu/main.cpp                             |   8 +-
 20 files changed, 364 insertions(+), 274 deletions(-)
 create mode 100644 src/core/hle/service/filesystem/romfs_controller.cpp
 create mode 100644 src/core/hle/service/filesystem/romfs_controller.h
 create mode 100644 src/core/hle/service/filesystem/save_data_controller.cpp
 create mode 100644 src/core/hle/service/filesystem/save_data_controller.h

diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 136c8dee6..8017eb58d 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -770,8 +770,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
     ASSERT(user_id);
 
     const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
-        EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
-        FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
+        {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, 1,
+        user_id->AsU128(), 0);
 
     const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
     if (!Common::FS::CreateParentDirs(full_path)) {
@@ -878,7 +878,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
                                                             FileSys::Mode::Read);
 
     const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
-        system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
+        {}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
         program_id, user_id->AsU128(), 0);
     return ToJString(env, user_save_data_path);
 }
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 753f55ebe..293d9647b 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -490,6 +490,10 @@ add_library(core STATIC
     hle/service/filesystem/fsp_pr.h
     hle/service/filesystem/fsp_srv.cpp
     hle/service/filesystem/fsp_srv.h
+    hle/service/filesystem/romfs_controller.cpp
+    hle/service/filesystem/romfs_controller.h
+    hle/service/filesystem/save_data_controller.cpp
+    hle/service/filesystem/save_data_controller.h
     hle/service/fgm/fgm.cpp
     hle/service/fgm/fgm.h
     hle/service/friend/friend.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c063f7719..461eea9c8 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -413,6 +413,7 @@ struct System::Impl {
         kernel.ShutdownCores();
         services.reset();
         service_manager.reset();
+        fs_controller.Reset();
         cheat_engine.reset();
         telemetry_session.reset();
         time_manager.Shutdown();
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 12b3bd797..23196cd5f 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -97,8 +97,9 @@ std::string SaveDataAttribute::DebugInfo() const {
                        static_cast<u8>(rank), index);
 }
 
-SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_)
-    : dir{std::move(save_directory_)}, system{system_} {
+SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
+                                 VirtualDir save_directory_)
+    : system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
     // Delete all temporary storages
     // On hardware, it is expected that temporary storage be empty at first use.
     dir->DeleteSubdirectoryRecursive("temp");
@@ -110,7 +111,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
     PrintSaveDataAttributeWarnings(meta);
 
     const auto save_directory =
-        GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+        GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
 
     return dir->CreateDirectoryRelative(save_directory);
 }
@@ -118,7 +119,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
 VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
 
     const auto save_directory =
-        GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+        GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
 
     auto out = dir->GetDirectoryRelative(save_directory);
 
@@ -147,14 +148,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
     }
 }
 
-std::string SaveDataFactory::GetFullPath(Core::System& system, VirtualDir dir,
+std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
                                          SaveDataSpaceId space, SaveDataType type, u64 title_id,
                                          u128 user_id, u64 save_id) {
     // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
     // be interpreted as the title id of the current process.
     if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
         if (title_id == 0) {
-            title_id = system.GetApplicationProcessProgramID();
+            title_id = program_id;
         }
     }
 
@@ -201,7 +202,7 @@ std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future)
 SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
                                                u128 user_id) const {
     const auto path =
-        GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+        GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
     const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
 
     const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
@@ -220,7 +221,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
 void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
                                         SaveDataSize new_value) const {
     const auto path =
-        GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+        GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
     const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
 
     const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index fd4887e99..30d96928e 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -87,10 +87,13 @@ constexpr const char* GetSaveDataSizeFileName() {
     return ".yuzu_save_size";
 }
 
+using ProgramId = u64;
+
 /// File system interface to the SaveData archive
 class SaveDataFactory {
 public:
-    explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
+    explicit SaveDataFactory(Core::System& system_, ProgramId program_id_,
+                             VirtualDir save_directory_);
     ~SaveDataFactory();
 
     VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
@@ -99,7 +102,7 @@ public:
     VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
 
     static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
-    static std::string GetFullPath(Core::System& system, VirtualDir dir, SaveDataSpaceId space,
+    static std::string GetFullPath(ProgramId program_id, VirtualDir dir, SaveDataSpaceId space,
                                    SaveDataType type, u64 title_id, u128 user_id, u64 save_id);
     static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
 
@@ -110,8 +113,9 @@ public:
     void SetAutoCreate(bool state);
 
 private:
-    VirtualDir dir;
     Core::System& system;
+    ProgramId program_id;
+    VirtualDir dir;
     bool auto_create{true};
 };
 
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9e05bdafa..a768bdc54 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -36,6 +36,7 @@
 #include "core/hle/service/caps/caps_su.h"
 #include "core/hle/service/caps/caps_types.h"
 #include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/hle/service/ns/ns.h"
 #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
@@ -2178,7 +2179,7 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) {
     attribute.type = FileSys::SaveDataType::SaveData;
 
     FileSys::VirtualDir save_data{};
-    const auto res = system.GetFileSystemController().CreateSaveData(
+    const auto res = system.GetFileSystemController().OpenSaveDataController()->CreateSaveData(
         &save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
 
     IPC::ResponseBuilder rb{ctx, 4};
@@ -2353,7 +2354,7 @@ void IApplicationFunctions::ExtendSaveData(HLERequestContext& ctx) {
               "new_journal={:016X}",
               static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
 
-    system.GetFileSystemController().WriteSaveDataSize(
+    system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
         type, system.GetApplicationProcessProgramID(), user_id,
         {new_normal_size, new_journal_size});
 
@@ -2378,7 +2379,7 @@ void IApplicationFunctions::GetSaveDataSize(HLERequestContext& ctx) {
     LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1],
               user_id[0]);
 
-    const auto size = system.GetFileSystemController().ReadSaveDataSize(
+    const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
         type, system.GetApplicationProcessProgramID(), user_id);
 
     IPC::ResponseBuilder rb{ctx, 6};
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 780f8c74d..ca6d8d607 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -24,15 +24,13 @@
 #include "core/hle/service/filesystem/fsp_ldr.h"
 #include "core/hle/service/filesystem/fsp_pr.h"
 #include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/hle/service/filesystem/romfs_controller.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
 #include "core/hle/service/server_manager.h"
 #include "core/loader/loader.h"
 
 namespace Service::FileSystem {
 
-// A default size for normal/journal save data size if application control metadata cannot be found.
-// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
-constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
-
 static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
                                                        std::string_view dir_name_) {
     std::string dir_name(Common::FS::SanitizePath(dir_name_));
@@ -297,145 +295,65 @@ FileSystemController::FileSystemController(Core::System& system_) : system{syste
 
 FileSystemController::~FileSystemController() = default;
 
-Result FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
-    romfs_factory = std::move(factory);
-    LOG_DEBUG(Service_FS, "Registered RomFS");
+Result FileSystemController::RegisterProcess(
+    ProcessId process_id, ProgramId program_id,
+    std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
+    std::scoped_lock lk{registration_lock};
+
+    registrations.emplace(process_id, Registration{
+                                          .program_id = program_id,
+                                          .romfs_factory = std::move(romfs_factory),
+                                          .save_data_factory = CreateSaveDataFactory(program_id),
+                                      });
+
+    LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
     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");
+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};
+
+    const auto it = registrations.find(process_id);
+    if (it == registrations.end()) {
+        return FileSys::ERROR_ENTITY_NOT_FOUND;
+    }
+
+    *out_program_id = it->second.program_id;
+    *out_save_data_controller =
+        std::make_shared<SaveDataController>(system, it->second.save_data_factory);
+    *out_romfs_controller =
+        std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
     return ResultSuccess;
 }
 
-Result FileSystemController::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;
-}
-
-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");
-    return ResultSuccess;
-}
-
-void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
+void FileSystemController::SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw) {
     LOG_TRACE(Service_FS, "Setting packed update for romfs");
 
-    if (romfs_factory == nullptr)
+    std::scoped_lock lk{registration_lock};
+    const auto it = registrations.find(process_id);
+    if (it == registrations.end()) {
         return;
+    }
 
-    romfs_factory->SetPackedUpdate(std::move(update_raw));
+    it->second.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());
+std::shared_ptr<SaveDataController> FileSystemController::OpenSaveDataController() {
+    return std::make_shared<SaveDataController>(system, CreateSaveDataFactory(ProgramId{}));
 }
 
-FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id,
-                                                            FileSys::ContentRecordType type) const {
-    LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
+std::shared_ptr<FileSys::SaveDataFactory> FileSystemController::CreateSaveDataFactory(
+    ProgramId program_id) {
+    using YuzuPath = Common::FS::YuzuPath;
+    const auto rw_mode = FileSys::Mode::ReadWrite;
 
-    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());
-
-    if (save_data_factory == nullptr) {
-        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;
-    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;
-    }
-
-    auto save_data = save_data_factory->Open(space, attribute);
-    if (save_data == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
-    }
-
-    *out_save_data = save_data;
-    return ResultSuccess;
-}
-
-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;
-    }
-
-    auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space);
-    if (save_data_space == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
-    }
-
-    *out_save_data_space = save_data_space;
-    return ResultSuccess;
+    auto vfs = system.GetFilesystem();
+    const auto nand_directory =
+        vfs->OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
+    return std::make_shared<FileSys::SaveDataFactory>(system, program_id,
+                                                      std::move(nand_directory));
 }
 
 Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
@@ -540,48 +458,6 @@ u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const {
     return 0;
 }
 
-FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataType type,
-                                                             u64 title_id, u128 user_id) const {
-    if (save_data_factory == nullptr) {
-        return {0, 0};
-    }
-
-    const auto value = save_data_factory->ReadSaveDataSize(type, title_id, user_id);
-
-    if (value.normal == 0 && value.journal == 0) {
-        FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE};
-
-        FileSys::NACP nacp;
-        const auto res = system.GetAppLoader().ReadControlData(nacp);
-
-        if (res != Loader::ResultStatus::Success) {
-            const FileSys::PatchManager pm{system.GetApplicationProcessProgramID(),
-                                           system.GetFileSystemController(),
-                                           system.GetContentProvider()};
-            const auto metadata = pm.GetControlMetadata();
-            const auto& nacp_unique = metadata.first;
-
-            if (nacp_unique != nullptr) {
-                new_size = {nacp_unique->GetDefaultNormalSaveSize(),
-                            nacp_unique->GetDefaultJournalSaveSize()};
-            }
-        } else {
-            new_size = {nacp.GetDefaultNormalSaveSize(), nacp.GetDefaultJournalSaveSize()};
-        }
-
-        WriteSaveDataSize(type, title_id, user_id, new_size);
-        return new_size;
-    }
-
-    return value;
-}
-
-void FileSystemController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
-                                             FileSys::SaveDataSize new_value) const {
-    if (save_data_factory != nullptr)
-        save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
-}
-
 void FileSystemController::SetGameCard(FileSys::VirtualFile file) {
     gamecard = std::make_unique<FileSys::XCI>(file);
     const auto dir = gamecard->ConcatenatedPseudoDirectory();
@@ -801,14 +677,9 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
     return bis_factory->GetBCATDirectory(title_id);
 }
 
-void FileSystemController::SetAutoSaveDataCreation(bool enable) {
-    save_data_factory->SetAutoCreate(enable);
-}
-
 void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
     if (overwrite) {
         bis_factory = nullptr;
-        save_data_factory = nullptr;
         sdmc_factory = nullptr;
     }
 
@@ -836,11 +707,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
                                        bis_factory->GetUserNANDContents());
     }
 
-    if (save_data_factory == nullptr) {
-        save_data_factory =
-            std::make_unique<FileSys::SaveDataFactory>(system, std::move(nand_directory));
-    }
-
     if (sdmc_factory == nullptr) {
         sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
                                                               std::move(sd_load_directory));
@@ -849,12 +715,19 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
     }
 }
 
+void FileSystemController::Reset() {
+    std::scoped_lock lk{registration_lock};
+    registrations.clear();
+}
+
 void LoopProcess(Core::System& system) {
     auto server_manager = std::make_unique<ServerManager>(system);
 
+    const auto FileSystemProxyFactory = [&] { return std::make_shared<FSP_SRV>(system); };
+
     server_manager->RegisterNamedService("fsp-ldr", std::make_shared<FSP_LDR>(system));
     server_manager->RegisterNamedService("fsp:pr", std::make_shared<FSP_PR>(system));
-    server_manager->RegisterNamedService("fsp-srv", std::make_shared<FSP_SRV>(system));
+    server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory));
     ServerManager::RunServer(std::move(server_manager));
 }
 
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 276d264e1..48f37d289 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -43,6 +43,9 @@ class ServiceManager;
 
 namespace FileSystem {
 
+class RomFsController;
+class SaveDataController;
+
 enum class ContentStorageId : u32 {
     System,
     User,
@@ -61,32 +64,24 @@ enum class OpenDirectoryMode : u64 {
 };
 DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
 
+using ProcessId = u64;
+using ProgramId = u64;
+
 class FileSystemController {
 public:
     explicit FileSystemController(Core::System& system_);
     ~FileSystemController();
 
-    Result RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
-    Result RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
-    Result RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
-    Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
+    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);
 
-    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;
+    std::shared_ptr<SaveDataController> OpenSaveDataController();
 
-    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 OpenSDMC(FileSys::VirtualDir* out_sdmc) const;
     Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition,
                             FileSys::BisPartitionId id) const;
@@ -96,11 +91,6 @@ public:
     u64 GetFreeSpaceSize(FileSys::StorageId id) const;
     u64 GetTotalSpaceSize(FileSys::StorageId id) const;
 
-    FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
-                                           u128 user_id) const;
-    void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
-                           FileSys::SaveDataSize new_value) const;
-
     void SetGameCard(FileSys::VirtualFile file);
     FileSys::XCI* GetGameCard() const;
 
@@ -133,15 +123,24 @@ public:
 
     FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
 
-    void SetAutoSaveDataCreation(bool enable);
-
     // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
     // above is called.
     void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
 
+    void Reset();
+
 private:
-    std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
-    std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
+    std::shared_ptr<FileSys::SaveDataFactory> CreateSaveDataFactory(ProgramId program_id);
+
+    struct Registration {
+        ProgramId program_id;
+        std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
+        std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
+    };
+
+    std::mutex registration_lock;
+    std::map<ProcessId, Registration> registrations;
+
     std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
     std::unique_ptr<FileSys::BISFactory> bis_factory;
 
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 82ecc1b90..a2397bec4 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -27,6 +27,8 @@
 #include "core/hle/result.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/hle/service/filesystem/romfs_controller.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
 #include "core/hle/service/hle_ipc.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/reporter.h"
@@ -577,9 +579,11 @@ private:
 
 class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
 public:
-    explicit ISaveDataInfoReader(Core::System& system_, FileSys::SaveDataSpaceId space,
-                                 FileSystemController& fsc_)
-        : ServiceFramework{system_, "ISaveDataInfoReader"}, fsc{fsc_} {
+    explicit ISaveDataInfoReader(Core::System& system_,
+                                 std::shared_ptr<SaveDataController> save_data_controller_,
+                                 FileSys::SaveDataSpaceId space)
+        : ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
+                                                                save_data_controller_} {
         static const FunctionInfo functions[] = {
             {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
         };
@@ -626,7 +630,7 @@ private:
 
     void FindAllSaves(FileSys::SaveDataSpaceId space) {
         FileSys::VirtualDir save_root{};
-        const auto result = fsc.OpenSaveDataSpace(&save_root, space);
+        const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
 
         if (result != ResultSuccess || save_root == nullptr) {
             LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
@@ -723,7 +727,8 @@ private:
     };
     static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
 
-    FileSystemController& fsc;
+    ProcessId process_id = 0;
+    std::shared_ptr<SaveDataController> save_data_controller;
     std::vector<SaveDataInfo> info;
     u64 next_entry_index = 0;
 };
@@ -863,21 +868,20 @@ FSP_SRV::FSP_SRV(Core::System& system_)
     if (Settings::values.enable_fs_access_log) {
         access_log_mode = AccessLogMode::SdCard;
     }
-
-    // This should be true on creation
-    fsc.SetAutoSaveDataCreation(true);
 }
 
 FSP_SRV::~FSP_SRV() = default;
 
 void FSP_SRV::SetCurrentProcess(HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    current_process_id = rp.Pop<u64>();
+    current_process_id = ctx.GetPID();
 
     LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
 
+    const auto res =
+        fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id);
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(res);
 }
 
 void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
@@ -916,7 +920,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
               uid[1], uid[0]);
 
     FileSys::VirtualDir save_data_dir{};
-    fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct);
+    save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser,
+                                         save_struct);
 
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
@@ -931,7 +936,8 @@ void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx)
     LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
 
     FileSys::VirtualDir save_data_dir{};
-    fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem, save_struct);
+    save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem,
+                                         save_struct);
 
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
@@ -950,7 +956,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
     LOG_INFO(Service_FS, "called.");
 
     FileSys::VirtualDir dir{};
-    auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute);
+    auto result =
+        save_data_controller->OpenSaveData(&dir, parameters.space_id, parameters.attribute);
     if (result != ResultSuccess) {
         IPC::ResponseBuilder rb{ctx, 2, 0, 0};
         rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
@@ -1001,7 +1008,7 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(ResultSuccess);
     rb.PushIpcInterface<ISaveDataInfoReader>(
-        std::make_shared<ISaveDataInfoReader>(system, space, fsc));
+        std::make_shared<ISaveDataInfoReader>(system, save_data_controller, space));
 }
 
 void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
@@ -1009,8 +1016,8 @@ void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(ResultSuccess);
-    rb.PushIpcInterface<ISaveDataInfoReader>(system, FileSys::SaveDataSpaceId::TemporaryStorage,
-                                             fsc);
+    rb.PushIpcInterface<ISaveDataInfoReader>(system, save_data_controller,
+                                             FileSys::SaveDataSpaceId::TemporaryStorage);
 }
 
 void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) {
@@ -1050,7 +1057,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
     LOG_DEBUG(Service_FS, "called");
 
     if (!romfs) {
-        auto current_romfs = fsc.OpenRomFSCurrentProcess();
+        auto current_romfs = romfs_controller->OpenRomFSCurrentProcess();
         if (!current_romfs) {
             // TODO (bunnei): Find the right error code to use here
             LOG_CRITICAL(Service_FS, "no file system interface available!");
@@ -1078,7 +1085,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
     LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
               storage_id, unknown, title_id);
 
-    auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
+    auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
 
     if (!data) {
         const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
@@ -1101,7 +1108,8 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
 
     const FileSys::PatchManager pm{title_id, fsc, content_provider};
 
-    auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
+    auto base =
+        romfs_controller->OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
     auto storage = std::make_shared<IStorage>(
         system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
 
@@ -1129,9 +1137,8 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
 
     LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
 
-    auto patched_romfs =
-        fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index,
-                                             FileSys::ContentRecordType::Program);
+    auto patched_romfs = romfs_controller->OpenPatchedRomFSWithProgramIndex(
+        program_id, program_index, FileSys::ContentRecordType::Program);
 
     if (!patched_romfs) {
         // TODO: Find the right error code to use here
@@ -1152,7 +1159,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
 void FSP_SRV::DisableAutoSaveDataCreation(HLERequestContext& ctx) {
     LOG_DEBUG(Service_FS, "called");
 
-    fsc.SetAutoSaveDataCreation(false);
+    save_data_controller->SetAutoCreate(false);
 
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 280bc9867..26980af99 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -17,6 +17,9 @@ class FileSystemBackend;
 
 namespace Service::FileSystem {
 
+class RomFsController;
+class SaveDataController;
+
 enum class AccessLogVersion : u32 {
     V7_0_0 = 2,
 
@@ -67,6 +70,9 @@ private:
     u64 current_process_id = 0;
     u32 access_log_program_index = 0;
     AccessLogMode access_log_mode = AccessLogMode::None;
+    u64 program_id = 0;
+    std::shared_ptr<SaveDataController> save_data_controller;
+    std::shared_ptr<RomFsController> romfs_controller;
 };
 
 } // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/romfs_controller.cpp b/src/core/hle/service/filesystem/romfs_controller.cpp
new file mode 100644
index 000000000..19c9cec72
--- /dev/null
+++ b/src/core/hle/service/filesystem/romfs_controller.cpp
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/filesystem/romfs_controller.h"
+
+namespace Service::FileSystem {
+
+RomFsController::RomFsController(std::shared_ptr<FileSys::RomFSFactory> factory_, u64 program_id_)
+    : factory{std::move(factory_)}, program_id{program_id_} {}
+RomFsController::~RomFsController() = default;
+
+FileSys::VirtualFile RomFsController::OpenRomFSCurrentProcess() {
+    return factory->OpenCurrentProcess(program_id);
+}
+
+FileSys::VirtualFile RomFsController::OpenPatchedRomFS(u64 title_id,
+                                                       FileSys::ContentRecordType type) {
+    return factory->OpenPatchedRomFS(title_id, type);
+}
+
+FileSys::VirtualFile RomFsController::OpenPatchedRomFSWithProgramIndex(
+    u64 title_id, u8 program_index, FileSys::ContentRecordType type) {
+    return factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
+}
+
+FileSys::VirtualFile RomFsController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+                                                FileSys::ContentRecordType type) {
+    return factory->Open(title_id, storage_id, type);
+}
+
+std::shared_ptr<FileSys::NCA> RomFsController::OpenBaseNca(u64 title_id,
+                                                           FileSys::StorageId storage_id,
+                                                           FileSys::ContentRecordType type) {
+    return factory->GetEntry(title_id, storage_id, type);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/romfs_controller.h b/src/core/hle/service/filesystem/romfs_controller.h
new file mode 100644
index 000000000..9a478f71d
--- /dev/null
+++ b/src/core/hle/service/filesystem/romfs_controller.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Service::FileSystem {
+
+class RomFsController {
+public:
+    explicit RomFsController(std::shared_ptr<FileSys::RomFSFactory> factory_, u64 program_id_);
+    ~RomFsController();
+
+    FileSys::VirtualFile OpenRomFSCurrentProcess();
+    FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type);
+    FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index,
+                                                          FileSys::ContentRecordType type);
+    FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
+                                   FileSys::ContentRecordType type);
+    std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id,
+                                              FileSys::ContentRecordType type);
+
+private:
+    const std::shared_ptr<FileSys::RomFSFactory> factory;
+    const u64 program_id;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/save_data_controller.cpp b/src/core/hle/service/filesystem/save_data_controller.cpp
new file mode 100644
index 000000000..d19b3ea1e
--- /dev/null
+++ b/src/core/hle/service/filesystem/save_data_controller.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/hle/service/filesystem/save_data_controller.h"
+#include "core/loader/loader.h"
+
+namespace Service::FileSystem {
+
+namespace {
+
+// A default size for normal/journal save data size if application control metadata cannot be found.
+// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
+constexpr u64 SufficientSaveDataSize = 0xF0000000;
+
+FileSys::SaveDataSize GetDefaultSaveDataSize(Core::System& system, u64 program_id) {
+    const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
+                                   system.GetContentProvider()};
+    const auto metadata = pm.GetControlMetadata();
+    const auto& nacp = metadata.first;
+
+    if (nacp != nullptr) {
+        return {nacp->GetDefaultNormalSaveSize(), nacp->GetDefaultJournalSaveSize()};
+    }
+
+    return {SufficientSaveDataSize, SufficientSaveDataSize};
+}
+
+} // namespace
+
+SaveDataController::SaveDataController(Core::System& system_,
+                                       std::shared_ptr<FileSys::SaveDataFactory> factory_)
+    : system{system_}, factory{std::move(factory_)} {}
+SaveDataController::~SaveDataController() = default;
+
+Result SaveDataController::CreateSaveData(FileSys::VirtualDir* out_save_data,
+                                          FileSys::SaveDataSpaceId space,
+                                          const FileSys::SaveDataAttribute& attribute) {
+    LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
+              attribute.DebugInfo());
+
+    auto save_data = factory->Create(space, attribute);
+    if (save_data == nullptr) {
+        return FileSys::ERROR_ENTITY_NOT_FOUND;
+    }
+
+    *out_save_data = save_data;
+    return ResultSuccess;
+}
+
+Result SaveDataController::OpenSaveData(FileSys::VirtualDir* out_save_data,
+                                        FileSys::SaveDataSpaceId space,
+                                        const FileSys::SaveDataAttribute& attribute) {
+    auto save_data = factory->Open(space, attribute);
+    if (save_data == nullptr) {
+        return FileSys::ERROR_ENTITY_NOT_FOUND;
+    }
+
+    *out_save_data = save_data;
+    return ResultSuccess;
+}
+
+Result SaveDataController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+                                             FileSys::SaveDataSpaceId space) {
+    auto save_data_space = factory->GetSaveDataSpaceDirectory(space);
+    if (save_data_space == nullptr) {
+        return FileSys::ERROR_ENTITY_NOT_FOUND;
+    }
+
+    *out_save_data_space = save_data_space;
+    return ResultSuccess;
+}
+
+FileSys::SaveDataSize SaveDataController::ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
+                                                           u128 user_id) {
+    const auto value = factory->ReadSaveDataSize(type, title_id, user_id);
+
+    if (value.normal == 0 && value.journal == 0) {
+        const auto size = GetDefaultSaveDataSize(system, title_id);
+        factory->WriteSaveDataSize(type, title_id, user_id, size);
+        return size;
+    }
+
+    return value;
+}
+
+void SaveDataController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
+                                           FileSys::SaveDataSize new_value) {
+    factory->WriteSaveDataSize(type, title_id, user_id, new_value);
+}
+
+void SaveDataController::SetAutoCreate(bool state) {
+    factory->SetAutoCreate(state);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/save_data_controller.h b/src/core/hle/service/filesystem/save_data_controller.h
new file mode 100644
index 000000000..863188e4c
--- /dev/null
+++ b/src/core/hle/service/filesystem/save_data_controller.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Service::FileSystem {
+
+class SaveDataController {
+public:
+    explicit SaveDataController(Core::System& system,
+                                std::shared_ptr<FileSys::SaveDataFactory> factory_);
+    ~SaveDataController();
+
+    Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+                          const FileSys::SaveDataAttribute& attribute);
+    Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space,
+                        const FileSys::SaveDataAttribute& attribute);
+    Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space,
+                             FileSys::SaveDataSpaceId space);
+
+    FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id);
+    void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
+                           FileSys::SaveDataSize new_value);
+    void SetAutoCreate(bool state);
+
+private:
+    Core::System& system;
+    const std::shared_ptr<FileSys::SaveDataFactory> factory;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index c9f8707b7..1e599e78b 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -216,20 +216,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
     }
 
-    // Find the RomFS by searching for a ".romfs" file in this directory
-    const auto& files = dir->GetFiles();
-    const auto romfs_iter =
-        std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& f) {
-            return f->GetName().find(".romfs") != std::string::npos;
-        });
-
-    // Register the RomFS if a ".romfs" file was found
-    if (romfs_iter != files.end() && *romfs_iter != nullptr) {
-        romfs = *romfs_iter;
-        system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
-            *this, system.GetContentProvider(), system.GetFileSystemController()));
-    }
-
     is_loaded = true;
     return {ResultStatus::Success,
             LoadParameters{metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()}};
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 814407535..2a32b1276 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -74,8 +74,10 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
         return load_result;
     }
 
-    system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
-        *this, system.GetContentProvider(), system.GetFileSystemController()));
+    system.GetFileSystemController().RegisterProcess(
+        process.GetProcessId(), nca->GetTitleId(),
+        std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
+                                                system.GetFileSystemController()));
 
     is_loaded = true;
     return load_result;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index e74697cda..83371fcbd 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -276,8 +276,10 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
     }
 
     if (romfs != nullptr) {
-        system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
-            *this, system.GetContentProvider(), system.GetFileSystemController()));
+        system.GetFileSystemController().RegisterProcess(
+            process.GetProcessId(), {},
+            std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
+                                                    system.GetFileSystemController()));
     }
 
     is_loaded = true;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index f4ab75b77..28116ff3a 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -111,7 +111,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
 
     FileSys::VirtualFile update_raw;
     if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
-        system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
+        system.GetFileSystemController().SetPackedUpdate(process.GetProcessId(),
+                                                         std::move(update_raw));
     }
 
     is_loaded = true;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 12d72c380..e9abb199a 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -78,7 +78,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
 
     FileSys::VirtualFile update_raw;
     if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
-        system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
+        system.GetFileSystemController().SetPackedUpdate(process.GetProcessId(),
+                                                         std::move(update_raw));
     }
 
     is_loaded = true;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4f4c75f5c..29f47bf48 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2292,14 +2292,14 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
             ASSERT(user_id);
 
             const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
-                *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
+                {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
                 FileSys::SaveDataType::SaveData, program_id, user_id->AsU128(), 0);
 
             path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
         } else {
             // Device save data
             const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath(
-                *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
+                {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
                 FileSys::SaveDataType::SaveData, program_id, {}, 0);
 
             path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path);
@@ -2662,8 +2662,8 @@ void GMainWindow::RemoveCacheStorage(u64 program_id) {
         vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
 
     const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath(
-        *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
-        FileSys::SaveDataType::CacheStorage, 0 /* program_id */, {}, 0);
+        {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::CacheStorage,
+        0 /* program_id */, {}, 0);
 
     const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
 

From 2a0d707ce1d4880dfcbd34c4d6572917a501f675 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Thu, 11 Jan 2024 16:50:59 -0500
Subject: [PATCH 19/33] shader_recompiler: emulate 8-bit and 16-bit storage
 writes with cas loop

---
 .../backend/spirv/emit_spirv_memory.cpp       | 40 ++++++++++++---
 .../backend/spirv/spirv_emit_context.cpp      | 51 +++++++++++++++++++
 .../backend/spirv/spirv_emit_context.h        |  3 ++
 3 files changed, 86 insertions(+), 8 deletions(-)

diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
index 8693801c7..bdcbccfde 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp
@@ -65,6 +65,14 @@ void WriteStorage32(EmitContext& ctx, const IR::Value& binding, const IR::Value&
     WriteStorage(ctx, binding, offset, value, ctx.storage_types.U32, sizeof(u32),
                  &StorageDefinitions::U32, index_offset);
 }
+
+void WriteStorageByCasLoop(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
+                           Id value, Id bit_offset, Id bit_count) {
+    const Id pointer{StoragePointer(ctx, binding, offset, ctx.storage_types.U32, sizeof(u32),
+                                    &StorageDefinitions::U32)};
+    ctx.OpFunctionCall(ctx.TypeVoid(), ctx.write_storage_cas_loop_func, pointer, value, bit_offset,
+                       bit_count);
+}
 } // Anonymous namespace
 
 void EmitLoadGlobalU8(EmitContext&) {
@@ -219,26 +227,42 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu
 
 void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
                         Id value) {
-    WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8,
-                 sizeof(u8), &StorageDefinitions::U8);
+    if (ctx.profile.support_int8) {
+        WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8,
+                     sizeof(u8), &StorageDefinitions::U8);
+    } else {
+        WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset8(offset), ctx.Const(8u));
+    }
 }
 
 void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
                         Id value) {
-    WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8,
-                 sizeof(s8), &StorageDefinitions::S8);
+    if (ctx.profile.support_int8) {
+        WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8,
+                     sizeof(s8), &StorageDefinitions::S8);
+    } else {
+        WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset8(offset), ctx.Const(8u));
+    }
 }
 
 void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
                          Id value) {
-    WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16,
-                 sizeof(u16), &StorageDefinitions::U16);
+    if (ctx.profile.support_int16) {
+        WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16,
+                     sizeof(u16), &StorageDefinitions::U16);
+    } else {
+        WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset16(offset), ctx.Const(16u));
+    }
 }
 
 void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
                          Id value) {
-    WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16,
-                 sizeof(s16), &StorageDefinitions::S16);
+    if (ctx.profile.support_int16) {
+        WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16,
+                     sizeof(s16), &StorageDefinitions::S16);
+    } else {
+        WriteStorageByCasLoop(ctx, binding, offset, value, ctx.BitOffset16(offset), ctx.Const(16u));
+    }
 }
 
 void EmitWriteStorage32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 0442adc83..a27f2f73a 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -480,6 +480,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
     DefineTextures(program.info, texture_binding, bindings.texture_scaling_index);
     DefineImages(program.info, image_binding, bindings.image_scaling_index);
     DefineAttributeMemAccess(program.info);
+    DefineWriteStorageCasLoopFunction(program.info);
     DefineGlobalMemoryFunctions(program.info);
     DefineRescalingInput(program.info);
     DefineRenderArea(program.info);
@@ -877,6 +878,56 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
     }
 }
 
+void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) {
+    if (profile.support_int8 && profile.support_int16) {
+        return;
+    }
+    if (!info.uses_int8 && !info.uses_int16) {
+        return;
+    }
+
+    AddCapability(spv::Capability::VariablePointersStorageBuffer);
+
+    const Id ptr_type{TypePointer(spv::StorageClass::StorageBuffer, U32[1])};
+    const Id func_type{TypeFunction(void_id, ptr_type, U32[1], U32[1], U32[1])};
+    const Id func{OpFunction(void_id, spv::FunctionControlMask::MaskNone, func_type)};
+    const Id pointer{OpFunctionParameter(ptr_type)};
+    const Id value{OpFunctionParameter(U32[1])};
+    const Id bit_offset{OpFunctionParameter(U32[1])};
+    const Id bit_count{OpFunctionParameter(U32[1])};
+
+    AddLabel();
+    const Id scope_device{Const(1u)};
+    const Id ordering_relaxed{u32_zero_value};
+    const Id body_label{OpLabel()};
+    const Id continue_label{OpLabel()};
+    const Id endloop_label{OpLabel()};
+    const Id beginloop_label{OpLabel()};
+    OpBranch(beginloop_label);
+
+    AddLabel(beginloop_label);
+    OpLoopMerge(endloop_label, continue_label, spv::LoopControlMask::MaskNone);
+    OpBranch(body_label);
+
+    AddLabel(body_label);
+    const Id expected_value{OpLoad(U32[1], pointer)};
+    const Id desired_value{OpBitFieldInsert(U32[1], expected_value, value, bit_offset, bit_count)};
+    const Id actual_value{OpAtomicCompareExchange(U32[1], pointer, scope_device, ordering_relaxed,
+                                                  ordering_relaxed, desired_value, expected_value)};
+    const Id store_successful{OpIEqual(U1, expected_value, actual_value)};
+    OpBranchConditional(store_successful, endloop_label, continue_label);
+
+    AddLabel(endloop_label);
+    OpReturn();
+
+    AddLabel(continue_label);
+    OpBranch(beginloop_label);
+
+    OpFunctionEnd();
+
+    write_storage_cas_loop_func = func;
+}
+
 void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
     if (!info.uses_global_memory || !profile.support_int64) {
         return;
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index 56019ad89..40adcb6b6 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -325,6 +325,8 @@ public:
     Id f32x2_min_cas{};
     Id f32x2_max_cas{};
 
+    Id write_storage_cas_loop_func{};
+
     Id load_global_func_u32{};
     Id load_global_func_u32x2{};
     Id load_global_func_u32x4{};
@@ -372,6 +374,7 @@ private:
     void DefineTextures(const Info& info, u32& binding, u32& scaling_index);
     void DefineImages(const Info& info, u32& binding, u32& scaling_index);
     void DefineAttributeMemAccess(const Info& info);
+    void DefineWriteStorageCasLoopFunction(const Info& info);
     void DefineGlobalMemoryFunctions(const Info& info);
     void DefineRescalingInput(const Info& info);
     void DefineRescalingInputPushConstant();

From 84787a2adaa58794ff72627ec2231da863b104c1 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Thu, 11 Jan 2024 18:57:07 -0500
Subject: [PATCH 20/33] ci: fix file mode check in format script

---
 .ci/scripts/format/script.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index 572fa9ffb..c22398de0 100755
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -25,7 +25,7 @@ for f in $FILES_TO_LINT; do
     "$CLANG_FORMAT" -i "$f"
 done
 
-DIFF=$(git diff)
+DIFF=$(git -c core.fileMode=false diff)
 
 if [ ! -z "$DIFF" ]; then
     echo "!!! Not compliant to coding style, here is the fix:"

From b5dac5f525e8d5884506ebd98a530e237b518480 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 7 Jan 2024 09:05:12 -0600
Subject: [PATCH 21/33] service: hid: Create abstracted pad structure

---
 src/android/app/src/main/jni/native.cpp       |   4 +-
 src/core/frontend/applets/controller.cpp      |   2 +-
 src/core/hle/service/hid/hid_server.cpp       |   2 +-
 src/hid_core/CMakeLists.txt                   |  34 ++
 src/hid_core/frontend/emulated_controller.cpp |  22 +-
 src/hid_core/hid_types.h                      |   3 +-
 src/hid_core/hid_util.h                       |   2 +-
 .../abstract_battery_handler.cpp              | 197 +++++++++++
 .../abstracted_pad/abstract_battery_handler.h |  49 +++
 .../abstract_button_handler.cpp               | 199 +++++++++++
 .../abstracted_pad/abstract_button_handler.h  |  75 ++++
 .../abstract_ir_sensor_handler.cpp            | 126 +++++++
 .../abstract_ir_sensor_handler.h              |  56 +++
 .../abstracted_pad/abstract_led_handler.cpp   | 123 +++++++
 .../abstracted_pad/abstract_led_handler.h     |  43 +++
 .../abstracted_pad/abstract_mcu_handler.cpp   | 108 ++++++
 .../abstracted_pad/abstract_mcu_handler.h     |  52 +++
 .../abstracted_pad/abstract_nfc_handler.cpp   | 140 ++++++++
 .../abstracted_pad/abstract_nfc_handler.h     |  57 ++++
 .../resources/abstracted_pad/abstract_pad.cpp | 294 ++++++++++++++++
 .../resources/abstracted_pad/abstract_pad.h   | 123 +++++++
 .../abstracted_pad/abstract_pad_holder.cpp    |  99 ++++++
 .../abstracted_pad/abstract_pad_holder.h      |  47 +++
 .../abstracted_pad/abstract_palma_handler.cpp |  47 +++
 .../abstracted_pad/abstract_palma_handler.h   |  37 ++
 .../abstract_properties_handler.cpp           | 322 ++++++++++++++++++
 .../abstract_properties_handler.h             |  86 +++++
 .../abstract_sixaxis_handler.cpp              | 154 +++++++++
 .../abstracted_pad/abstract_sixaxis_handler.h |  61 ++++
 .../abstract_vibration_handler.cpp            |  73 ++++
 .../abstract_vibration_handler.h              |  51 +++
 src/hid_core/resources/npad/npad.cpp          |   8 +-
 src/hid_core/resources/npad/npad_data.cpp     |   2 +-
 src/hid_core/resources/npad/npad_types.h      |  99 ++++++
 .../resources/npad/npad_vibration.cpp         |  80 +++++
 src/hid_core/resources/npad/npad_vibration.h  |  34 ++
 src/hid_core/resources/six_axis/six_axis.cpp  |   6 +-
 .../vibration/gc_vibration_device.cpp         | 106 ++++++
 .../resources/vibration/gc_vibration_device.h |  31 ++
 .../vibration/n64_vibration_device.cpp        |  80 +++++
 .../vibration/n64_vibration_device.h          |  29 ++
 .../resources/vibration/vibration_base.cpp    |  30 ++
 .../resources/vibration/vibration_base.h      |  28 ++
 .../resources/vibration/vibration_device.cpp  |  84 +++++
 .../resources/vibration/vibration_device.h    |  35 ++
 src/yuzu/applets/qt_controller.cpp            |   8 +-
 src/yuzu/applets/qt_software_keyboard.cpp     |   2 +-
 .../configuration/configure_input_player.cpp  |  18 +-
 .../configure_input_player_widget.cpp         |   2 +-
 src/yuzu/main.cpp                             |   2 +-
 src/yuzu/util/controller_navigation.cpp       |   4 +-
 51 files changed, 3333 insertions(+), 43 deletions(-)
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_button_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_led_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_pad.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_pad.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_pad_holder.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_palma_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_properties_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp
 create mode 100644 src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h
 create mode 100644 src/hid_core/resources/npad/npad_vibration.cpp
 create mode 100644 src/hid_core/resources/npad/npad_vibration.h
 create mode 100644 src/hid_core/resources/vibration/gc_vibration_device.cpp
 create mode 100644 src/hid_core/resources/vibration/gc_vibration_device.h
 create mode 100644 src/hid_core/resources/vibration/n64_vibration_device.cpp
 create mode 100644 src/hid_core/resources/vibration/n64_vibration_device.h
 create mode 100644 src/hid_core/resources/vibration/vibration_base.cpp
 create mode 100644 src/hid_core/resources/vibration/vibration_base.h
 create mode 100644 src/hid_core/resources/vibration/vibration_device.cpp
 create mode 100644 src/hid_core/resources/vibration/vibration_device.h

diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 136c8dee6..e436622e0 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -410,8 +410,8 @@ void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
         jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
 
         if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
-            handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
-            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+            handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
             handheld->Disconnect();
         }
     }
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
index 34fe23b6a..e04d884ba 100644
--- a/src/core/frontend/applets/controller.cpp
+++ b/src/core/frontend/applets/controller.cpp
@@ -47,7 +47,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
         // Connect controllers based on the following priority list from highest to lowest priority:
         // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
         if (parameters.allow_pro_controller) {
-            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
             controller->Connect(true);
         } else if (parameters.allow_dual_joycons) {
             controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 74898888a..1951da33b 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -1498,7 +1498,7 @@ void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
     bool check_device_index = false;
 
     switch (vibration_device_handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/hid_core/CMakeLists.txt b/src/hid_core/CMakeLists.txt
index cce4e6857..aa85502b5 100644
--- a/src/hid_core/CMakeLists.txt
+++ b/src/hid_core/CMakeLists.txt
@@ -36,6 +36,30 @@ add_library(hid_core STATIC
     irsensor/processor_base.h
     irsensor/tera_plugin_processor.cpp
     irsensor/tera_plugin_processor.h
+    resources/abstracted_pad/abstract_battery_handler.cpp
+    resources/abstracted_pad/abstract_battery_handler.h
+    resources/abstracted_pad/abstract_button_handler.cpp
+    resources/abstracted_pad/abstract_button_handler.h
+    resources/abstracted_pad/abstract_ir_sensor_handler.cpp
+    resources/abstracted_pad/abstract_ir_sensor_handler.h
+    resources/abstracted_pad/abstract_led_handler.cpp
+    resources/abstracted_pad/abstract_led_handler.h
+    resources/abstracted_pad/abstract_mcu_handler.cpp
+    resources/abstracted_pad/abstract_mcu_handler.h
+    resources/abstracted_pad/abstract_nfc_handler.cpp
+    resources/abstracted_pad/abstract_nfc_handler.h
+    resources/abstracted_pad/abstract_pad.cpp
+    resources/abstracted_pad/abstract_pad.h
+    resources/abstracted_pad/abstract_pad_holder.cpp
+    resources/abstracted_pad/abstract_pad_holder.h
+    resources/abstracted_pad/abstract_palma_handler.cpp
+    resources/abstracted_pad/abstract_palma_handler.h
+    resources/abstracted_pad/abstract_properties_handler.cpp
+    resources/abstracted_pad/abstract_properties_handler.h
+    resources/abstracted_pad/abstract_sixaxis_handler.cpp
+    resources/abstracted_pad/abstract_sixaxis_handler.h
+    resources/abstracted_pad/abstract_vibration_handler.cpp
+    resources/abstracted_pad/abstract_vibration_handler.h
     resources/debug_pad/debug_pad.cpp
     resources/debug_pad/debug_pad.h
     resources/debug_pad/debug_pad_types.h
@@ -56,6 +80,8 @@ add_library(hid_core STATIC
     resources/npad/npad_resource.cpp
     resources/npad/npad_resource.h
     resources/npad/npad_types.h
+    resources/npad/npad_vibration.cpp
+    resources/npad/npad_vibration.h
     resources/palma/palma.cpp
     resources/palma/palma.h
     resources/six_axis/console_six_axis.cpp
@@ -78,6 +104,14 @@ add_library(hid_core STATIC
     resources/touch_screen/touch_types.h
     resources/unique_pad/unique_pad.cpp
     resources/unique_pad/unique_pad.h
+    resources/vibration/gc_vibration_device.h
+    resources/vibration/gc_vibration_device.cpp
+    resources/vibration/n64_vibration_device.h
+    resources/vibration/n64_vibration_device.cpp
+    resources/vibration/vibration_base.h
+    resources/vibration/vibration_base.cpp
+    resources/vibration/vibration_device.h
+    resources/vibration/vibration_device.cpp
     resources/applet_resource.cpp
     resources/applet_resource.h
     resources/controller_base.cpp
diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp
index 3d2d1e9f9..a6a96935d 100644
--- a/src/hid_core/frontend/emulated_controller.cpp
+++ b/src/hid_core/frontend/emulated_controller.cpp
@@ -27,7 +27,7 @@ EmulatedController::~EmulatedController() = default;
 NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
     switch (type) {
     case Settings::ControllerType::ProController:
-        return NpadStyleIndex::ProController;
+        return NpadStyleIndex::Fullkey;
     case Settings::ControllerType::DualJoyconDetached:
         return NpadStyleIndex::JoyconDual;
     case Settings::ControllerType::LeftJoycon:
@@ -49,13 +49,13 @@ NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerTyp
     case Settings::ControllerType::SegaGenesis:
         return NpadStyleIndex::SegaGenesis;
     default:
-        return NpadStyleIndex::ProController;
+        return NpadStyleIndex::Fullkey;
     }
 }
 
 Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
         return Settings::ControllerType::ProController;
     case NpadStyleIndex::JoyconDual:
         return Settings::ControllerType::DualJoyconDetached;
@@ -106,7 +106,7 @@ void EmulatedController::ReloadFromSettings() {
         SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
         original_npad_type = npad_type;
     } else {
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         original_npad_type = npad_type;
     }
 
@@ -1073,7 +1073,7 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
         .body = GetNpadColor(controller.color_values[index].body),
         .button = GetNpadColor(controller.color_values[index].buttons),
     };
-    if (npad_type == NpadStyleIndex::ProController) {
+    if (npad_type == NpadStyleIndex::Fullkey) {
         controller.colors_state.left = {
             .body = GetNpadColor(controller.color_values[index].left_grip),
             .button = GetNpadColor(controller.color_values[index].buttons),
@@ -1356,7 +1356,7 @@ bool EmulatedController::HasNfc() const {
     switch (npad_type) {
     case NpadStyleIndex::JoyconRight:
     case NpadStyleIndex::JoyconDual:
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
     case NpadStyleIndex::Handheld:
         break;
     default:
@@ -1548,7 +1548,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
     // Fallback Fullkey controllers to Pro controllers
     if (IsControllerFullkey() && supported_style_tag.fullkey) {
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         Connect();
         return;
     }
@@ -1556,13 +1556,13 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
     // Fallback Dual joycon controllers to Pro controllers
     if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
-        SetNpadStyleIndex(NpadStyleIndex::ProController);
+        SetNpadStyleIndex(NpadStyleIndex::Fullkey);
         Connect();
         return;
     }
 
     // Fallback Pro controllers to Dual joycon
-    if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) {
+    if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) {
         LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
         SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
         Connect();
@@ -1577,7 +1577,7 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
     std::scoped_lock lock{mutex};
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
     case NpadStyleIndex::GameCube:
     case NpadStyleIndex::NES:
     case NpadStyleIndex::SNES:
@@ -1593,7 +1593,7 @@ bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
     std::scoped_lock lock{mutex};
     const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
     switch (type) {
-    case NpadStyleIndex::ProController:
+    case NpadStyleIndex::Fullkey:
         return supported_style_tag.fullkey.As<bool>();
     case NpadStyleIndex::Handheld:
         return supported_style_tag.handheld.As<bool>();
diff --git a/src/hid_core/hid_types.h b/src/hid_core/hid_types.h
index a81ed6af0..2c3f02f34 100644
--- a/src/hid_core/hid_types.h
+++ b/src/hid_core/hid_types.h
@@ -220,6 +220,7 @@ enum class NpadIdType : u32 {
 };
 
 enum class NpadInterfaceType : u8 {
+    None = 0,
     Bluetooth = 1,
     Rail = 2,
     Usb = 3,
@@ -229,7 +230,7 @@ enum class NpadInterfaceType : u8 {
 // This is nn::hid::NpadStyleIndex
 enum class NpadStyleIndex : u8 {
     None = 0,
-    ProController = 3,
+    Fullkey = 3,
     Handheld = 4,
     HandheldNES = 4,
     JoyconDual = 5,
diff --git a/src/hid_core/hid_util.h b/src/hid_core/hid_util.h
index 94ff2d23a..397a87472 100644
--- a/src/hid_core/hid_util.h
+++ b/src/hid_core/hid_util.h
@@ -42,7 +42,7 @@ constexpr Result IsSixaxisHandleValid(const Core::HID::SixAxisSensorHandle& hand
 
 constexpr Result IsVibrationHandleValid(const Core::HID::VibrationDeviceHandle& handle) {
     switch (handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
new file mode 100644
index 000000000..62fbbb0a7
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp
@@ -0,0 +1,197 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/core_timing.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_battery_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractBatteryHandler::NpadAbstractBatteryHandler() {}
+
+NpadAbstractBatteryHandler::~NpadAbstractBatteryHandler() = default;
+
+void NpadAbstractBatteryHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractBatteryHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractBatteryHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractBatteryHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractBatteryHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+Result NpadAbstractBatteryHandler::UpdateBatteryState(u64 aruid) {
+    const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId());
+    AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+    if (aruid_data == nullptr) {
+        return ResultSuccess;
+    }
+
+    auto& npad_internal_state =
+        aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
+    auto& system_properties = npad_internal_state.system_properties;
+
+    system_properties.is_charging_joy_dual.Assign(dual_battery.is_charging);
+    system_properties.is_powered_joy_dual.Assign(dual_battery.is_powered);
+    system_properties.is_charging_joy_left.Assign(left_battery.is_charging);
+    system_properties.is_powered_joy_left.Assign(left_battery.is_powered);
+    system_properties.is_charging_joy_right.Assign(right_battery.is_charging);
+    system_properties.is_powered_joy_right.Assign(right_battery.is_powered);
+
+    npad_internal_state.battery_level_dual = dual_battery.battery_level;
+    npad_internal_state.battery_level_left = left_battery.battery_level;
+    npad_internal_state.battery_level_right = right_battery.battery_level;
+
+    return ResultSuccess;
+}
+
+void NpadAbstractBatteryHandler::UpdateBatteryState() {
+    if (ref_counter == 0) {
+        return;
+    }
+    has_new_battery_data = GetNewBatteryState();
+}
+
+bool NpadAbstractBatteryHandler::GetNewBatteryState() {
+    bool has_changed = false;
+    Core::HID::NpadPowerInfo new_dual_battery_state{};
+    Core::HID::NpadPowerInfo new_left_battery_state{};
+    Core::HID::NpadPowerInfo new_right_battery_state{};
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        const auto power_info = abstract_pad->power_info;
+        if (power_info.battery_level > Core::HID::NpadBatteryLevel::Full) {
+            // Abort
+            continue;
+        }
+
+        const auto style = abstract_pad->assignment_style;
+
+        if (style.is_external_assigned || style.is_handheld_assigned) {
+            new_dual_battery_state = power_info;
+        }
+        if (style.is_external_left_assigned || style.is_handheld_left_assigned) {
+            new_left_battery_state = power_info;
+        }
+        if (style.is_external_right_assigned || style.is_handheld_right_assigned) {
+            new_right_battery_state = power_info;
+        }
+
+        if (abstract_pad->internal_flags.is_battery_low_ovln_required) {
+            if (abstract_pad->interface_type == Core::HID::NpadInterfaceType::Rail) {
+                // TODO
+            }
+            abstract_pad->internal_flags.is_battery_low_ovln_required.Assign(false);
+        }
+    }
+
+    if (dual_battery.battery_level != new_dual_battery_state.battery_level ||
+        dual_battery.is_charging != new_dual_battery_state.is_charging ||
+        dual_battery.is_powered != new_dual_battery_state.is_powered) {
+        has_changed = true;
+        dual_battery = new_dual_battery_state;
+    }
+
+    if (left_battery.battery_level != new_left_battery_state.battery_level ||
+        left_battery.is_charging != new_left_battery_state.is_charging ||
+        left_battery.is_powered != new_left_battery_state.is_powered) {
+        has_changed = true;
+        left_battery = new_left_battery_state;
+    }
+
+    if (right_battery.battery_level != new_right_battery_state.battery_level ||
+        right_battery.is_charging != new_right_battery_state.is_charging ||
+        right_battery.is_powered != new_right_battery_state.is_powered) {
+        has_changed = true;
+        right_battery = new_right_battery_state;
+    }
+
+    return has_changed;
+}
+
+void NpadAbstractBatteryHandler::UpdateCoreBatteryState() {
+    if (ref_counter == 0) {
+        return;
+    }
+    if (!has_new_battery_data) {
+        return;
+    }
+
+    UpdateBatteryState(0);
+}
+
+void NpadAbstractBatteryHandler::InitializeBatteryState(u64 aruid) {
+    UpdateBatteryState(aruid);
+}
+
+bool NpadAbstractBatteryHandler::HasBattery() const {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        const auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        return abstract_pad->disabled_feature_set.has_fullkey_battery ||
+               abstract_pad->disabled_feature_set.has_left_right_joy_battery;
+    }
+
+    return false;
+}
+
+void NpadAbstractBatteryHandler::HasLeftRightBattery(bool& has_left, bool& has_right) const {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    has_left = false;
+    has_right = false;
+
+    for (std::size_t i = 0; i < count; i++) {
+        const auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (!abstract_pad->disabled_feature_set.has_fullkey_battery &&
+            !abstract_pad->disabled_feature_set.has_left_right_joy_battery) {
+            continue;
+        }
+        has_left = abstract_pad->assignment_style.is_external_left_assigned ||
+                   abstract_pad->assignment_style.is_handheld_left_assigned;
+        has_right = abstract_pad->assignment_style.is_external_right_assigned ||
+                    abstract_pad->assignment_style.is_handheld_right_assigned;
+    }
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
new file mode 100644
index 000000000..85ac5eb72
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractBatteryHandler final {
+public:
+    explicit NpadAbstractBatteryHandler();
+    ~NpadAbstractBatteryHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    Result UpdateBatteryState(u64 aruid);
+    void UpdateBatteryState();
+    bool GetNewBatteryState();
+    void UpdateCoreBatteryState();
+    void InitializeBatteryState(u64 aruid);
+
+    bool HasBattery() const;
+    void HasLeftRightBattery(bool& has_left, bool& has_right) const;
+
+private:
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+    Core::HID::NpadPowerInfo dual_battery{};
+    Core::HID::NpadPowerInfo left_battery{};
+    Core::HID::NpadPowerInfo right_battery{};
+    bool has_new_battery_data{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
new file mode 100644
index 000000000..587169433
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp
@@ -0,0 +1,199 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_button_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractButtonHandler::NpadAbstractButtonHandler() {}
+
+NpadAbstractButtonHandler::~NpadAbstractButtonHandler() = default;
+
+void NpadAbstractButtonHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractButtonHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractButtonHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractButtonHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractButtonHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+Result NpadAbstractButtonHandler::UpdateAllButtonWithHomeProtection(u64 aruid) {
+    const Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+
+    if (data == nullptr) {
+        return ResultSuccess;
+    }
+
+    auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+    UpdateButtonLifo(npad_entry, aruid);
+
+    bool is_home_button_protection_enabled{};
+    const auto result = applet_resource_holder->shared_npad_resource->GetHomeProtectionEnabled(
+        is_home_button_protection_enabled, aruid, npad_id);
+
+    if (result.IsError()) {
+        return ResultSuccess;
+    }
+
+    npad_entry.internal_state.button_properties.is_home_button_protection_enabled.Assign(
+        is_home_button_protection_enabled);
+
+    return ResultSuccess;
+}
+
+void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
+    Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    for (std::size_t i = 0; i < AruidIndexMax; i++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+        auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+        UpdateButtonLifo(npad_entry, data->aruid);
+    }
+}
+
+void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
+    Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    for (std::size_t i = 0; i < AruidIndexMax; i++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+        auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+        UpdateButtonLifo(npad_entry, data->aruid);
+    }
+}
+
+void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
+    Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+    if (data == nullptr) {
+        return;
+    }
+    auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+    UpdateButtonLifo(npad_entry, aruid);
+}
+
+Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
+    const Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    auto result = applet_resource_holder->shared_npad_resource->SetHomeProtectionEnabled(
+        aruid, npad_id, is_enabled);
+    if (result.IsError()) {
+        return result;
+    }
+
+    auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+    if (data == nullptr) {
+        return ResultSuccess;
+    }
+
+    bool is_home_protection_enabled{};
+    result = applet_resource_holder->shared_npad_resource->GetHomeProtectionEnabled(
+        is_home_protection_enabled, aruid, npad_id);
+    if (result.IsError()) {
+        return ResultSuccess;
+    }
+
+    auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+    npad_entry.internal_state.button_properties.is_home_button_protection_enabled.Assign(
+        is_home_protection_enabled);
+    return ResultSuccess;
+}
+
+bool NpadAbstractButtonHandler::IsButtonPressedOnConsoleMode() {
+    return is_button_pressed_on_console_mode;
+}
+
+void NpadAbstractButtonHandler::EnableCenterClamp() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        abstract_pad->internal_flags.use_center_clamp.Assign(true);
+    }
+}
+
+void NpadAbstractButtonHandler::UpdateButtonLifo(NpadSharedMemoryEntry& shared_memory, u64 aruid) {
+    auto* npad_resource = applet_resource_holder->shared_npad_resource;
+    Core::HID::NpadStyleTag style_tag = {properties_handler->GetStyleSet(aruid)};
+    style_tag.system_ext.Assign(npad_resource->GetActiveData()->GetNpadSystemExtState());
+
+    UpdateNpadFullkeyLifo(style_tag, 0, aruid, shared_memory);
+    UpdateHandheldLifo(style_tag, 1, aruid, shared_memory);
+    UpdateJoyconDualLifo(style_tag, 2, aruid, shared_memory);
+    UpdateJoyconLeftLifo(style_tag, 3, aruid, shared_memory);
+    UpdateJoyconRightLifo(style_tag, 4, aruid, shared_memory);
+    UpdatePalmaLifo(style_tag, 5, aruid, shared_memory);
+    UpdateSystemExtLifo(style_tag, 6, aruid, shared_memory);
+}
+
+void NpadAbstractButtonHandler::UpdateNpadFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+                                                      int style_index, u64 aruid,
+                                                      NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateHandheldLifo(Core::HID::NpadStyleTag style_tag,
+                                                   int style_index, u64 aruid,
+                                                   NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconDualLifo(Core::HID::NpadStyleTag style_tag,
+                                                     int style_index, u64 aruid,
+                                                     NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconLeftLifo(Core::HID::NpadStyleTag style_tag,
+                                                     int style_index, u64 aruid,
+                                                     NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateJoyconRightLifo(Core::HID::NpadStyleTag style_tag,
+                                                      int style_index, u64 aruid,
+                                                      NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdateSystemExtLifo(Core::HID::NpadStyleTag style_tag,
+                                                    int style_index, u64 aruid,
+                                                    NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+void NpadAbstractButtonHandler::UpdatePalmaLifo(Core::HID::NpadStyleTag style_tag, int style_index,
+                                                u64 aruid, NpadSharedMemoryEntry& shared_memory) {
+    // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.h b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h
new file mode 100644
index 000000000..01eafe96d
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct NpadSharedMemoryEntry;
+
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractButtonHandler final {
+public:
+    explicit NpadAbstractButtonHandler();
+    ~NpadAbstractButtonHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    Result UpdateAllButtonWithHomeProtection(u64 aruid);
+
+    void UpdateAllButtonLifo();
+    void UpdateCoreBatteryState();
+    void UpdateButtonState(u64 aruid);
+
+    Result SetHomeProtection(bool is_enabled, u64 aruid);
+    bool IsButtonPressedOnConsoleMode();
+    void EnableCenterClamp();
+
+    void UpdateButtonLifo(NpadSharedMemoryEntry& shared_memory, u64 aruid);
+
+    void UpdateNpadFullkeyLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                               NpadSharedMemoryEntry& shared_memory);
+    void UpdateHandheldLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                            NpadSharedMemoryEntry& shared_memory);
+    void UpdateJoyconDualLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                              NpadSharedMemoryEntry& shared_memory);
+    void UpdateJoyconLeftLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                              NpadSharedMemoryEntry& shared_memory);
+    void UpdateJoyconRightLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                               NpadSharedMemoryEntry& shared_memory);
+    void UpdateSystemExtLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                             NpadSharedMemoryEntry& shared_memory);
+    void UpdatePalmaLifo(Core::HID::NpadStyleTag style_tag, int index, u64 aruid,
+                         NpadSharedMemoryEntry& shared_memory);
+
+private:
+    struct GcTrigger {
+        float left;
+        float right;
+    };
+
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+
+    bool is_button_pressed_on_console_mode{};
+
+    u64 gc_sampling_number{};
+    GcTrigger gc_trigger_state{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp
new file mode 100644
index 000000000..d4e4181bf
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractIrSensorHandler::NpadAbstractIrSensorHandler() {}
+
+NpadAbstractIrSensorHandler::~NpadAbstractIrSensorHandler() = default;
+
+void NpadAbstractIrSensorHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractIrSensorHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractIrSensorHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractIrSensorHandler::UpdateIrSensorState() {
+    const auto previous_state = sensor_state;
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    if (count == 0) {
+        sensor_state = NpadIrSensorState::Disabled;
+        if (sensor_state == previous_state) {
+            return;
+        }
+        ir_sensor_event->Signal();
+        return;
+    }
+
+    bool is_found{};
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (!abstract_pad->disabled_feature_set.has_bluetooth_address) {
+            continue;
+        }
+        is_found = true;
+        xcd_handle = abstract_pad->xcd_handle;
+    }
+
+    if (is_found) {
+        if (sensor_state == NpadIrSensorState::Active) {
+            return;
+        }
+        sensor_state = NpadIrSensorState::Available;
+        if (sensor_state == previous_state) {
+            return;
+        }
+        ir_sensor_event->Signal();
+        return;
+    }
+
+    sensor_state = NpadIrSensorState::Unavailable;
+    if (sensor_state == previous_state) {
+        return;
+    }
+
+    ir_sensor_event->Signal();
+    return;
+}
+
+Result NpadAbstractIrSensorHandler::ActivateIrSensor(bool is_enabled) {
+    if (sensor_state == NpadIrSensorState::Unavailable) {
+        return ResultIrSensorIsNotReady;
+    }
+    if (is_enabled && sensor_state == NpadIrSensorState::Available) {
+        sensor_state = NpadIrSensorState::Active;
+    } else {
+        if (is_enabled) {
+            return ResultSuccess;
+        }
+        if (sensor_state != NpadIrSensorState::Active) {
+            return ResultSuccess;
+        }
+        sensor_state = NpadIrSensorState::Available;
+    }
+    ir_sensor_event->Signal();
+    return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::GetIrSensorEventHandle(Kernel::KReadableEvent** out_event) {
+    *out_event = &ir_sensor_event->GetReadableEvent();
+    return ResultSuccess;
+}
+
+Result NpadAbstractIrSensorHandler::GetXcdHandleForNpadWithIrSensor(u64& handle) const {
+    if (sensor_state < NpadIrSensorState::Available) {
+        return ResultIrSensorIsNotReady;
+    }
+    handle = xcd_handle;
+    return ResultSuccess;
+}
+
+NpadIrSensorState NpadAbstractIrSensorHandler::GetSensorState() const {
+    return sensor_state;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h
new file mode 100644
index 000000000..fe8e005af
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+enum class NpadIrSensorState : u32 {
+    Disabled,
+    Unavailable,
+    Available,
+    Active,
+};
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractIrSensorHandler final {
+public:
+    explicit NpadAbstractIrSensorHandler();
+    ~NpadAbstractIrSensorHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void UpdateIrSensorState();
+    Result ActivateIrSensor(bool param_2);
+
+    Result GetIrSensorEventHandle(Kernel::KReadableEvent** out_event);
+
+    Result GetXcdHandleForNpadWithIrSensor(u64& handle) const;
+
+    NpadIrSensorState GetSensorState() const;
+
+private:
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+    Kernel::KEvent* ir_sensor_event{nullptr};
+    u64 xcd_handle{};
+    NpadIrSensorState sensor_state{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp
new file mode 100644
index 000000000..0b2bfe88d
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/core_timing.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_led_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractLedHandler::NpadAbstractLedHandler() {}
+
+NpadAbstractLedHandler::~NpadAbstractLedHandler() = default;
+
+void NpadAbstractLedHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractLedHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractLedHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractLedHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractLedHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractLedHandler::SetNpadLedHandlerLedPattern() {
+    const auto npad_id = properties_handler->GetNpadId();
+
+    switch (npad_id) {
+    case Core::HID::NpadIdType::Player1:
+        left_pattern = Core::HID::LedPattern{1, 0, 0, 0};
+        break;
+    case Core::HID::NpadIdType::Player2:
+        left_pattern = Core::HID::LedPattern{1, 1, 0, 0};
+        break;
+    case Core::HID::NpadIdType::Player3:
+        left_pattern = Core::HID::LedPattern{1, 1, 1, 0};
+        break;
+    case Core::HID::NpadIdType::Player4:
+        left_pattern = Core::HID::LedPattern{1, 1, 1, 1};
+        break;
+    case Core::HID::NpadIdType::Player5:
+        left_pattern = Core::HID::LedPattern{1, 0, 0, 1};
+        break;
+    case Core::HID::NpadIdType::Player6:
+        left_pattern = Core::HID::LedPattern{1, 0, 1, 0};
+        break;
+    case Core::HID::NpadIdType::Player7:
+        left_pattern = Core::HID::LedPattern{1, 0, 1, 1};
+        break;
+    case Core::HID::NpadIdType::Player8:
+        left_pattern = Core::HID::LedPattern{0, 1, 1, 0};
+        break;
+    case Core::HID::NpadIdType::Other:
+    case Core::HID::NpadIdType::Handheld:
+        left_pattern = Core::HID::LedPattern{0, 0, 0, 0};
+        break;
+    default:
+        ASSERT_MSG(false, "Invalid npad id type");
+        break;
+    }
+
+    switch (npad_id) {
+    case Core::HID::NpadIdType::Player1:
+        right_pattern = Core::HID::LedPattern{0, 0, 0, 1};
+        break;
+    case Core::HID::NpadIdType::Player2:
+        right_pattern = Core::HID::LedPattern{0, 1, 1, 1};
+        break;
+    case Core::HID::NpadIdType::Player3:
+        right_pattern = Core::HID::LedPattern{0, 1, 1, 1};
+        break;
+    case Core::HID::NpadIdType::Player4:
+        right_pattern = Core::HID::LedPattern{1, 1, 1, 1};
+        break;
+    case Core::HID::NpadIdType::Player5:
+        right_pattern = Core::HID::LedPattern{1, 0, 0, 1};
+        break;
+    case Core::HID::NpadIdType::Player6:
+        right_pattern = Core::HID::LedPattern{0, 1, 0, 1};
+        break;
+    case Core::HID::NpadIdType::Player7:
+        right_pattern = Core::HID::LedPattern{1, 1, 0, 1};
+        break;
+    case Core::HID::NpadIdType::Player8:
+        right_pattern = Core::HID::LedPattern{0, 1, 1, 0};
+        break;
+    case Core::HID::NpadIdType::Other:
+    case Core::HID::NpadIdType::Handheld:
+        right_pattern = Core::HID::LedPattern{0, 0, 0, 0};
+        break;
+    default:
+        ASSERT_MSG(false, "Invalid npad id type");
+        break;
+    }
+}
+
+void NpadAbstractLedHandler::SetLedBlinkingDevice(Core::HID::LedPattern pattern) {
+    led_blinking = pattern;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.h b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h
new file mode 100644
index 000000000..09528129b
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractLedHandler final {
+public:
+    explicit NpadAbstractLedHandler();
+    ~NpadAbstractLedHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void SetNpadLedHandlerLedPattern();
+
+    void SetLedBlinkingDevice(Core::HID::LedPattern pattern);
+
+private:
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+    Core::HID::LedPattern led_blinking{0, 0, 0, 0};
+    Core::HID::LedPattern left_pattern{0, 0, 0, 0};
+    Core::HID::LedPattern right_pattern{0, 0, 0, 0};
+    u64 led_interval{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp
new file mode 100644
index 000000000..6f35bd95c
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_mcu_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractMcuHandler::NpadAbstractMcuHandler() {}
+
+NpadAbstractMcuHandler::~NpadAbstractMcuHandler() = default;
+
+void NpadAbstractMcuHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractMcuHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractMcuHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractMcuHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractMcuHandler::UpdateMcuState() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = properties_handler->GetAbstractedPads(abstract_pads);
+
+    if (count == 0) {
+        mcu_holder = {};
+        return;
+    }
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (!abstract_pad->disabled_feature_set.has_left_joy_rail_bus) {
+            if (!abstract_pad->disabled_feature_set.has_left_joy_six_axis_sensor &&
+                !abstract_pad->disabled_feature_set.has_right_joy_six_axis_sensor) {
+                continue;
+            }
+            if (mcu_holder[1].state != NpadMcuState::Active) {
+                mcu_holder[1].state = NpadMcuState::Available;
+            }
+            mcu_holder[1].abstracted_pad = abstract_pad;
+            continue;
+        }
+        if (mcu_holder[0].state != NpadMcuState::Active) {
+            mcu_holder[0].state = NpadMcuState::Available;
+        }
+        mcu_holder[0].abstracted_pad = abstract_pad;
+    }
+}
+
+Result NpadAbstractMcuHandler::GetAbstractedPad(IAbstractedPad** data, u32 mcu_index) {
+    if (mcu_holder[mcu_index].state == NpadMcuState::None ||
+        mcu_holder[mcu_index].abstracted_pad == nullptr) {
+        return ResultMcuIsNotReady;
+    }
+    *data = mcu_holder[mcu_index].abstracted_pad;
+    return ResultSuccess;
+}
+
+NpadMcuState NpadAbstractMcuHandler::GetMcuState(u32 mcu_index) {
+    return mcu_holder[mcu_index].state;
+}
+
+Result NpadAbstractMcuHandler::SetMcuState(bool is_enabled, u32 mcu_index) {
+    NpadMcuState& state = mcu_holder[mcu_index].state;
+
+    if (state == NpadMcuState::None) {
+        return ResultMcuIsNotReady;
+    }
+
+    if ((is_enabled) && (state == NpadMcuState::Available)) {
+        state = NpadMcuState::Active;
+        return ResultSuccess;
+    }
+
+    if (is_enabled) {
+        return ResultSuccess;
+    }
+    if (state != NpadMcuState::Active) {
+        return ResultSuccess;
+    }
+
+    state = NpadMcuState::Available;
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h
new file mode 100644
index 000000000..9902dd03a
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct IAbstractedPad;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+enum class NpadMcuState : u32 {
+    None,
+    Available,
+    Active,
+};
+
+struct NpadMcuHolder {
+    NpadMcuState state;
+    INSERT_PADDING_BYTES(0x4);
+    IAbstractedPad* abstracted_pad;
+};
+static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractMcuHandler final {
+public:
+    explicit NpadAbstractMcuHandler();
+    ~NpadAbstractMcuHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void UpdateMcuState();
+    Result GetAbstractedPad(IAbstractedPad** data, u32 mcu_index);
+    NpadMcuState GetMcuState(u32 mcu_index);
+    Result SetMcuState(bool is_enabled, u32 mcu_index);
+
+private:
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+    std::array<NpadMcuHolder, 2> mcu_holder{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp
new file mode 100644
index 000000000..bd9b79333
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_nfc_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+NpadAbstractNfcHandler::NpadAbstractNfcHandler() {}
+
+NpadAbstractNfcHandler::~NpadAbstractNfcHandler() = default;
+
+void NpadAbstractNfcHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractNfcHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+Result NpadAbstractNfcHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractNfcHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractNfcHandler::UpdateNfcState() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = properties_handler->GetAbstractedPads(abstract_pads);
+
+    if (count == 0) {
+        if (sensor_state == NpadNfcState::Active) {
+            nfc_activate_event->Signal();
+        }
+        if (sensor_state == NpadNfcState::Unavailable) {
+            return;
+        }
+        sensor_state = NpadNfcState::Unavailable;
+        input_event->Signal();
+        return;
+    }
+
+    bool is_found{};
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (!abstract_pad->disabled_feature_set.has_nfc) {
+            continue;
+        }
+        is_found = true;
+        xcd_handle = 0;
+    }
+
+    if (is_found) {
+        if (sensor_state == NpadNfcState::Active) {
+            return;
+        }
+        if (sensor_state == NpadNfcState::Available) {
+            return;
+        }
+        sensor_state = NpadNfcState::Available;
+        input_event->Signal();
+        return;
+    }
+
+    if (sensor_state == NpadNfcState::Active) {
+        nfc_activate_event->Signal();
+    }
+    if (sensor_state == NpadNfcState::Unavailable) {
+        return;
+    }
+    sensor_state = NpadNfcState::Unavailable;
+    input_event->Signal();
+    return;
+}
+
+bool NpadAbstractNfcHandler::HasNfcSensor() {
+    return sensor_state != NpadNfcState::Unavailable;
+}
+
+bool NpadAbstractNfcHandler::IsNfcActivated() {
+    return sensor_state == NpadNfcState::Active;
+}
+
+Result NpadAbstractNfcHandler::GetAcquireNfcActivateEventHandle(
+    Kernel::KReadableEvent** out_event) {
+    *out_event = &nfc_activate_event->GetReadableEvent();
+    return ResultSuccess;
+}
+
+void NpadAbstractNfcHandler::SetInputEvent(Kernel::KEvent* event) {
+    input_event = event;
+}
+
+Result NpadAbstractNfcHandler::ActivateNfc(bool is_enabled) {
+    if (sensor_state == NpadNfcState::Active) {
+        return ResultNfcIsNotReady;
+    }
+
+    NpadNfcState new_state = NpadNfcState::Available;
+    if (is_enabled) {
+        new_state = NpadNfcState::Active;
+    }
+    if (sensor_state != new_state) {
+        sensor_state = new_state;
+        nfc_activate_event->Signal();
+    }
+    return ResultSuccess;
+}
+
+Result NpadAbstractNfcHandler::GetXcdHandleWithNfc(u64& out_xcd_handle) const {
+    if (sensor_state == NpadNfcState::Unavailable) {
+        return ResultNfcIsNotReady;
+    }
+    if (xcd_handle == 0) {
+        return ResultNfcXcdHandleIsNotInitialized;
+    }
+
+    out_xcd_handle = xcd_handle;
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h
new file mode 100644
index 000000000..0702722a6
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Kernel {
+class KReadableEvent;
+}
+
+enum class NpadNfcState : u32 {
+    Unavailable,
+    Available,
+    Active,
+};
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractNfcHandler final {
+public:
+    explicit NpadAbstractNfcHandler();
+    ~NpadAbstractNfcHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void UpdateNfcState();
+    bool HasNfcSensor();
+    bool IsNfcActivated();
+
+    Result GetAcquireNfcActivateEventHandle(Kernel::KReadableEvent** out_event);
+    void SetInputEvent(Kernel::KEvent* event);
+
+    Result ActivateNfc(bool is_enabled);
+
+    Result GetXcdHandleWithNfc(u64& out_xcd_handle) const;
+
+private:
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    s32 ref_counter{};
+    Kernel::KEvent* nfc_activate_event{nullptr};
+    Kernel::KEvent* input_event{nullptr};
+    u64 xcd_handle{};
+    NpadNfcState sensor_state{NpadNfcState::Unavailable};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.cpp b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp
new file mode 100644
index 000000000..2c7691d7c
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp
@@ -0,0 +1,294 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+AbstractPad::AbstractPad() {}
+
+AbstractPad::~AbstractPad() = default;
+
+void AbstractPad::SetExternals(AppletResourceHolder* applet_resource,
+                               CaptureButtonResource* capture_button_resource,
+                               HomeButtonResource* home_button_resource,
+                               SixAxisResource* sixaxis_resource, PalmaResource* palma_resource,
+                               VibrationHandler* vibration) {
+    applet_resource_holder = applet_resource;
+
+    properties_handler.SetAppletResource(applet_resource_holder);
+    properties_handler.SetAbstractPadHolder(&abstract_pad_holder);
+
+    led_handler.SetAppletResource(applet_resource_holder);
+    led_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    led_handler.SetPropertiesHandler(&properties_handler);
+
+    ir_sensor_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    ir_sensor_handler.SetPropertiesHandler(&properties_handler);
+
+    nfc_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    nfc_handler.SetPropertiesHandler(&properties_handler);
+
+    mcu_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    mcu_handler.SetPropertiesHandler(&properties_handler);
+
+    std::array<NpadVibrationDevice*, 2> vibration_devices{&vibration_left, &vibration_right};
+    vibration_handler.SetAppletResource(applet_resource_holder);
+    vibration_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    vibration_handler.SetPropertiesHandler(&properties_handler);
+    vibration_handler.SetN64Vibration(&vibration_n64);
+    vibration_handler.SetVibration(vibration_devices);
+    vibration_handler.SetGcVibration(&vibration_gc);
+
+    sixaxis_handler.SetAppletResource(applet_resource_holder);
+    sixaxis_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    sixaxis_handler.SetPropertiesHandler(&properties_handler);
+    sixaxis_handler.SetSixaxisResource(sixaxis_resource);
+
+    button_handler.SetAppletResource(applet_resource_holder);
+    button_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    button_handler.SetPropertiesHandler(&properties_handler);
+
+    battery_handler.SetAppletResource(applet_resource_holder);
+    battery_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    battery_handler.SetPropertiesHandler(&properties_handler);
+
+    palma_handler.SetAbstractPadHolder(&abstract_pad_holder);
+    palma_handler.SetPropertiesHandler(&properties_handler);
+    palma_handler.SetPalmaResource(palma_resource);
+}
+
+void AbstractPad::SetNpadId(Core::HID::NpadIdType npad_id) {
+    properties_handler.SetNpadId(npad_id);
+}
+
+Result AbstractPad::Activate() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+
+    if (ref_counter != 0) {
+        ref_counter++;
+        return ResultSuccess;
+    }
+
+    std::size_t stage = 0;
+    Result result = ResultSuccess;
+
+    if (result.IsSuccess()) {
+        stage++;
+        result = properties_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = led_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = ir_sensor_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = mcu_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = nfc_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = vibration_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = sixaxis_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = button_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = battery_handler.IncrementRefCounter();
+    }
+    if (result.IsSuccess()) {
+        stage++;
+        result = palma_handler.IncrementRefCounter();
+    }
+
+    if (result.IsSuccess()) {
+        ref_counter++;
+        return result;
+    }
+
+    if (stage > 9) {
+        battery_handler.DecrementRefCounter();
+    }
+    if (stage > 8) {
+        button_handler.DecrementRefCounter();
+    }
+    if (stage > 7) {
+        sixaxis_handler.DecrementRefCounter();
+    }
+    if (stage > 6) {
+        vibration_handler.DecrementRefCounter();
+    }
+    if (stage > 5) {
+        nfc_handler.DecrementRefCounter();
+    }
+    if (stage > 4) {
+        mcu_handler.DecrementRefCounter();
+    }
+    if (stage > 3) {
+        ir_sensor_handler.DecrementRefCounter();
+    }
+    if (stage > 2) {
+        led_handler.DecrementRefCounter();
+    }
+    if (stage > 1) {
+        properties_handler.DecrementRefCounter();
+    }
+    return result;
+}
+
+Result AbstractPad::Deactivate() {
+    if (ref_counter == 0) {
+        return ResultNpadResourceNotInitialized;
+    }
+
+    ref_counter--;
+    battery_handler.DecrementRefCounter();
+    button_handler.DecrementRefCounter();
+    sixaxis_handler.DecrementRefCounter();
+    vibration_handler.DecrementRefCounter();
+    nfc_handler.DecrementRefCounter();
+    ir_sensor_handler.DecrementRefCounter();
+    mcu_handler.DecrementRefCounter();
+    led_handler.DecrementRefCounter();
+    properties_handler.DecrementRefCounter();
+    palma_handler.DecrementRefCounter();
+
+    return ResultSuccess;
+}
+
+Result AbstractPad::ActivateNpad(u64 aruid) {
+    Result result = ResultSuccess;
+    if (result.IsSuccess()) {
+        result = properties_handler.ActivateNpadUnknown0x88(aruid);
+    }
+    if (result.IsSuccess()) {
+        result = sixaxis_handler.UpdateSixAxisState2(aruid);
+    }
+    if (result.IsSuccess()) {
+        result = battery_handler.UpdateBatteryState(aruid);
+    }
+    return result;
+}
+
+NpadAbstractedPadHolder* AbstractPad::GetAbstractedPadHolder() {
+    return &abstract_pad_holder;
+}
+
+NpadAbstractPropertiesHandler* AbstractPad::GetAbstractPropertiesHandler() {
+    return &properties_handler;
+}
+
+NpadAbstractLedHandler* AbstractPad::GetAbstractLedHandler() {
+    return &led_handler;
+}
+
+NpadAbstractIrSensorHandler* AbstractPad::GetAbstractIrSensorHandler() {
+    return &ir_sensor_handler;
+}
+
+NpadAbstractMcuHandler* AbstractPad::GetAbstractMcuHandler() {
+    return &mcu_handler;
+}
+
+NpadAbstractNfcHandler* AbstractPad::GetAbstractNfcHandler() {
+    return &nfc_handler;
+}
+
+NpadAbstractVibrationHandler* AbstractPad::GetAbstractVibrationHandler() {
+    return &vibration_handler;
+}
+
+NpadAbstractSixAxisHandler* AbstractPad::GetAbstractSixAxisHandler() {
+    return &sixaxis_handler;
+}
+
+NpadAbstractButtonHandler* AbstractPad::GetAbstractButtonHandler() {
+    return &button_handler;
+}
+
+NpadAbstractBatteryHandler* AbstractPad::GetAbstractBatteryHandler() {
+    return &battery_handler;
+}
+
+NpadN64VibrationDevice* AbstractPad::GetN64VibrationDevice() {
+    return &vibration_n64;
+}
+
+NpadVibrationDevice* AbstractPad::GetVibrationDevice(Core::HID::DeviceIndex device_index) {
+    if (device_index == Core::HID::DeviceIndex::Right) {
+        return &vibration_right;
+    }
+    return &vibration_left;
+}
+
+void AbstractPad::GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list) {
+    list.emplace_back(&vibration_left);
+    list.emplace_back(&vibration_right);
+}
+
+NpadGcVibrationDevice* AbstractPad::GetGCVibrationDevice() {
+    return &vibration_gc;
+}
+
+Core::HID::NpadIdType AbstractPad::GetLastActiveNpad() {
+    return properties_handler.GetNpadId();
+}
+
+void AbstractPad::UpdateInterfaceType() {
+    if (interface_type != properties_handler.GetInterfaceType()) {
+        Update();
+    }
+    battery_handler.UpdateBatteryState();
+}
+
+void AbstractPad::Update() {
+    properties_handler.UpdateDeviceType();
+    led_handler.SetNpadLedHandlerLedPattern();
+    vibration_handler.UpdateVibrationState();
+    sixaxis_handler.UpdateSixAxisState();
+    nfc_handler.UpdateNfcState();
+    ir_sensor_handler.UpdateIrSensorState();
+    mcu_handler.UpdateMcuState();
+    palma_handler.UpdatePalmaState();
+    battery_handler.UpdateBatteryState();
+    button_handler.EnableCenterClamp();
+
+    interface_type = properties_handler.GetInterfaceType();
+
+    std::scoped_lock lock{*applet_resource_holder->shared_mutex};
+    properties_handler.UpdateAllDeviceProperties();
+    battery_handler.UpdateCoreBatteryState();
+    button_handler.UpdateCoreBatteryState();
+}
+
+void AbstractPad::UpdatePadState() {
+    button_handler.UpdateAllButtonLifo();
+    sixaxis_handler.UpdateSixAxisState();
+    battery_handler.UpdateCoreBatteryState();
+}
+
+void AbstractPad::EnableAppletToGetInput(u64 aruid) {
+    button_handler.UpdateButtonState(aruid);
+    sixaxis_handler.UpdateSixAxisState(aruid);
+    battery_handler.UpdateBatteryState(aruid);
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.h b/src/hid_core/resources/abstracted_pad/abstract_pad.h
new file mode 100644
index 000000000..cbdf84af7
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+#include "hid_core/resources/abstracted_pad/abstract_battery_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_button_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_led_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_mcu_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_nfc_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_palma_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_vibration_handler.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+#include "hid_core/resources/vibration/vibration_device.h"
+
+namespace Service::HID {
+class AppletResource;
+class SixAxisResource;
+class PalmaResource;
+class NPadResource;
+class AbstractPad;
+class NpadLastActiveHandler;
+class NpadIrNfcHandler;
+class UniquePads;
+class NpadPalmaHandler;
+class FirmwareResource;
+class NpadVibration;
+class NpadHighestBattery;
+class NpadGcVibration;
+
+class CaptureButtonResource;
+class HomeButtonResource;
+class VibrationHandler;
+
+struct HandheldConfig;
+
+/// Handles Npad request from HID interfaces
+class AbstractPad final {
+public:
+    explicit AbstractPad();
+    ~AbstractPad();
+
+    void SetExternals(AppletResourceHolder* applet_resource,
+                      CaptureButtonResource* capture_button_resource,
+                      HomeButtonResource* home_button_resource, SixAxisResource* sixaxis_resource,
+                      PalmaResource* palma_resource, VibrationHandler* vibration);
+    void SetNpadId(Core::HID::NpadIdType npad_id);
+
+    Result Activate();
+    Result Deactivate();
+
+    Result ActivateNpad(u64 aruid);
+
+    NpadAbstractedPadHolder* GetAbstractedPadHolder();
+    NpadAbstractPropertiesHandler* GetAbstractPropertiesHandler();
+    NpadAbstractLedHandler* GetAbstractLedHandler();
+    NpadAbstractIrSensorHandler* GetAbstractIrSensorHandler();
+    NpadAbstractMcuHandler* GetAbstractMcuHandler();
+    NpadAbstractNfcHandler* GetAbstractNfcHandler();
+    NpadAbstractVibrationHandler* GetAbstractVibrationHandler();
+    NpadAbstractSixAxisHandler* GetAbstractSixAxisHandler();
+    NpadAbstractButtonHandler* GetAbstractButtonHandler();
+    NpadAbstractBatteryHandler* GetAbstractBatteryHandler();
+
+    NpadN64VibrationDevice* GetN64VibrationDevice();
+    NpadVibrationDevice* GetVibrationDevice(Core::HID::DeviceIndex device_index);
+    void GetLeftRightVibrationDevice(std::vector<NpadVibrationDevice*> list);
+    NpadGcVibrationDevice* GetGCVibrationDevice();
+
+    Core::HID::NpadIdType GetLastActiveNpad();
+    void UpdateInterfaceType();
+    void Update();
+
+    void UpdatePadState();
+    void EnableAppletToGetInput(u64 aruid);
+
+private:
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder abstract_pad_holder{};
+    NpadAbstractPropertiesHandler properties_handler{};
+    NpadAbstractLedHandler led_handler{};
+    NpadAbstractIrSensorHandler ir_sensor_handler{};
+    NpadAbstractNfcHandler nfc_handler{};
+    NpadAbstractMcuHandler mcu_handler{};
+    NpadAbstractVibrationHandler vibration_handler{};
+    NpadAbstractSixAxisHandler sixaxis_handler{};
+    NpadAbstractButtonHandler button_handler{};
+    NpadAbstractBatteryHandler battery_handler{};
+    NpadAbstractPalmaHandler palma_handler{};
+
+    NpadN64VibrationDevice vibration_n64{};
+    NpadVibrationDevice vibration_left{};
+    NpadVibrationDevice vibration_right{};
+    NpadGcVibrationDevice vibration_gc{};
+
+    // SixAxisConfigHolder fullkey_config;
+    // SixAxisConfigHolder handheld_config;
+    // SixAxisConfigHolder dual_left_config;
+    // SixAxisConfigHolder dual_right_config;
+    // SixAxisConfigHolder left_config;
+    // SixAxisConfigHolder right_config;
+
+    s32 ref_counter{};
+    Core::HID::NpadInterfaceType interface_type{Core::HID::NpadInterfaceType::None};
+};
+
+using FullAbstractPad = std::array<AbstractPad, MaxSupportedNpadIdTypes>;
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp
new file mode 100644
index 000000000..8334dc34f
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+
+Result NpadAbstractedPadHolder::RegisterAbstractPad(IAbstractedPad* abstracted_pad) {
+    if (list_size >= assignment_list.size()) {
+        return ResultNpadIsNotProController;
+    }
+
+    for (std::size_t i = 0; i < list_size; i++) {
+        if (assignment_list[i].device_type == abstracted_pad->device_type) {
+            return ResultNpadIsNotProController;
+        }
+    }
+
+    assignment_list[list_size] = {
+        .abstracted_pad = abstracted_pad,
+        .device_type = abstracted_pad->device_type,
+        .interface_type = abstracted_pad->interface_type,
+        .controller_id = abstracted_pad->controller_id,
+    };
+
+    list_size++;
+    return ResultSuccess;
+}
+
+void NpadAbstractedPadHolder::RemoveAbstractPadByControllerId(u64 controller_id) {
+    if (list_size == 0) {
+        return;
+    }
+    if (controller_id == 0) {
+        return;
+    }
+    for (std::size_t i = 0; i < list_size; i++) {
+        if (assignment_list[i].controller_id != controller_id) {
+            continue;
+        }
+        for (std::size_t e = i + 1; e < list_size; e++) {
+            assignment_list[e - 1] = assignment_list[e];
+        }
+        list_size--;
+        return;
+    }
+}
+
+void NpadAbstractedPadHolder::DetachAbstractedPad() {
+    while (list_size > 0) {
+        for (std::size_t i = 1; i < list_size; i++) {
+            assignment_list[i - 1] = assignment_list[i];
+        }
+        list_size--;
+    }
+}
+
+u64 NpadAbstractedPadHolder::RemoveAbstractPadByAssignmentStyle(
+    Service::HID::AssignmentStyle assignment_style) {
+    for (std::size_t i = 0; i < list_size; i++) {
+        if ((assignment_style.raw & assignment_list[i].abstracted_pad->assignment_style.raw) == 0) {
+            continue;
+        }
+        for (std::size_t e = i + 1; e < list_size; e++) {
+            assignment_list[e - 1] = assignment_list[e];
+        }
+        list_size--;
+        return list_size;
+    }
+    return list_size;
+}
+
+u32 NpadAbstractedPadHolder::GetAbstractedPads(std::span<IAbstractedPad*> list) const {
+    u32 num_elements = std::min(static_cast<u32>(list.size()), list_size);
+    for (std::size_t i = 0; i < num_elements; i++) {
+        list[i] = assignment_list[i].abstracted_pad;
+    }
+    return num_elements;
+}
+
+void NpadAbstractedPadHolder::SetAssignmentMode(const NpadJoyAssignmentMode& mode) {
+    assignment_mode = mode;
+}
+
+NpadJoyAssignmentMode NpadAbstractedPadHolder::GetAssignmentMode() const {
+    return assignment_mode;
+}
+
+std::size_t NpadAbstractedPadHolder::GetStyleIndexList(
+    std::span<Core::HID::NpadStyleIndex> list) const {
+    for (std::size_t i = 0; i < list_size; i++) {
+        list[i] = assignment_list[i].device_type;
+    }
+    return list_size;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h
new file mode 100644
index 000000000..fb7f472e8
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_pad_holder.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+struct IAbstractedPad;
+
+struct AbstractAssignmentHolder {
+    IAbstractedPad* abstracted_pad;
+    Core::HID::NpadStyleIndex device_type;
+    Core::HID::NpadInterfaceType interface_type;
+    INSERT_PADDING_BYTES(0x6);
+    u64 controller_id;
+};
+static_assert(sizeof(AbstractAssignmentHolder) == 0x18,
+              "AbstractAssignmentHolder  is an invalid size");
+
+/// This is nn::hid::server::NpadAbstractedPadHolder
+class NpadAbstractedPadHolder final {
+public:
+    Result RegisterAbstractPad(IAbstractedPad* abstracted_pad);
+    void RemoveAbstractPadByControllerId(u64 controller_id);
+    void DetachAbstractedPad();
+    u64 RemoveAbstractPadByAssignmentStyle(Service::HID::AssignmentStyle assignment_style);
+    u32 GetAbstractedPads(std::span<IAbstractedPad*> list) const;
+
+    void SetAssignmentMode(const NpadJoyAssignmentMode& mode);
+    NpadJoyAssignmentMode GetAssignmentMode() const;
+
+    std::size_t GetStyleIndexList(std::span<Core::HID::NpadStyleIndex> list) const;
+
+private:
+    std::array<AbstractAssignmentHolder, 5> assignment_list{};
+    u32 list_size{};
+    NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp
new file mode 100644
index 000000000..04d276d61
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/abstracted_pad/abstract_palma_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+
+namespace Service::HID {
+
+NpadAbstractPalmaHandler::NpadAbstractPalmaHandler() {}
+
+NpadAbstractPalmaHandler::~NpadAbstractPalmaHandler() = default;
+
+void NpadAbstractPalmaHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractPalmaHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+    return;
+}
+
+void NpadAbstractPalmaHandler::SetPalmaResource(PalmaResource* resource) {
+    palma_resource = resource;
+}
+
+Result NpadAbstractPalmaHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractPalmaHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractPalmaHandler::UpdatePalmaState() {
+    // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h
new file mode 100644
index 000000000..fbd2e67e5
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+class PalmaResource;
+
+class NpadAbstractPalmaHandler final {
+public:
+    explicit NpadAbstractPalmaHandler();
+    ~NpadAbstractPalmaHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+    void SetPalmaResource(PalmaResource* resource);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void UpdatePalmaState();
+
+private:
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+    PalmaResource* palma_resource{nullptr};
+
+    s32 ref_counter{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp
new file mode 100644
index 000000000..4897a2784
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp
@@ -0,0 +1,322 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractPropertiesHandler::NpadAbstractPropertiesHandler() {}
+
+NpadAbstractPropertiesHandler::~NpadAbstractPropertiesHandler() = default;
+
+void NpadAbstractPropertiesHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+    return;
+}
+
+void NpadAbstractPropertiesHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+    return;
+}
+
+void NpadAbstractPropertiesHandler::SetNpadId(Core::HID::NpadIdType npad_id) {
+    if (!IsNpadIdValid(npad_id)) {
+        ASSERT_MSG(false, "Invalid npad id");
+    }
+
+    npad_id_type = npad_id;
+}
+
+Core::HID::NpadIdType NpadAbstractPropertiesHandler::GetNpadId() const {
+    return npad_id_type;
+}
+
+Result NpadAbstractPropertiesHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+
+    if (ref_counter != 0) {
+        ref_counter++;
+        return ResultSuccess;
+    }
+
+    const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+    for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+        auto& internal_state =
+            data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
+        if (!data->flag.is_assigned) {
+            continue;
+        }
+        internal_state.fullkey_lifo.buffer_count = 0;
+        internal_state.handheld_lifo.buffer_count = 0;
+        internal_state.joy_dual_lifo.buffer_count = 0;
+        internal_state.joy_left_lifo.buffer_count = 0;
+        internal_state.joy_right_lifo.buffer_count = 0;
+        internal_state.palma_lifo.buffer_count = 0;
+        internal_state.system_ext_lifo.buffer_count = 0;
+        internal_state.gc_trigger_lifo.buffer_count = 0;
+        internal_state.sixaxis_fullkey_lifo.lifo.buffer_count = 0;
+        internal_state.sixaxis_handheld_lifo.lifo.buffer_count = 0;
+        internal_state.sixaxis_dual_left_lifo.lifo.buffer_count = 0;
+        internal_state.sixaxis_dual_right_lifo.lifo.buffer_count = 0;
+        internal_state.sixaxis_left_lifo.lifo.buffer_count = 0;
+        internal_state.sixaxis_right_lifo.lifo.buffer_count = 0;
+
+        internal_state.style_tag = {Core::HID::NpadStyleSet::None};
+        internal_state.assignment_mode = NpadJoyAssignmentMode::Dual;
+        internal_state.joycon_color = {};
+        internal_state.fullkey_color = {};
+
+        internal_state.system_properties.raw = 0;
+        internal_state.button_properties.raw = 0;
+        internal_state.device_type.raw = 0;
+
+        internal_state.battery_level_dual = Core::HID::NpadBatteryLevel::Empty;
+        internal_state.battery_level_left = Core::HID::NpadBatteryLevel::Empty;
+        internal_state.battery_level_right = Core::HID::NpadBatteryLevel::Empty;
+
+        internal_state.applet_footer_type = AppletFooterUiType::None;
+        internal_state.applet_footer_attributes = {};
+        internal_state.lark_type_l_and_main = {};
+        internal_state.lark_type_r = {};
+
+        internal_state.sixaxis_fullkey_properties.is_newly_assigned.Assign(true);
+        internal_state.sixaxis_handheld_properties.is_newly_assigned.Assign(true);
+        internal_state.sixaxis_dual_left_properties.is_newly_assigned.Assign(true);
+        internal_state.sixaxis_dual_right_properties.is_newly_assigned.Assign(true);
+        internal_state.sixaxis_left_properties.is_newly_assigned.Assign(true);
+        internal_state.sixaxis_right_properties.is_newly_assigned.Assign(true);
+    }
+
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractPropertiesHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+Result NpadAbstractPropertiesHandler::ActivateNpadUnknown0x88(u64 aruid) {
+    const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+    for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+        if (!data->flag.is_assigned || data->aruid != aruid) {
+            continue;
+        }
+        UpdateDeviceProperties(aruid, data->shared_memory_format->npad.npad_entry[npad_index]);
+        return ResultSuccess;
+    }
+    return ResultSuccess;
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceType() {
+    // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceColor() {
+    // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateFooterAttributes() {
+    // TODO
+}
+
+void NpadAbstractPropertiesHandler::UpdateAllDeviceProperties() {
+    const auto npad_index = NpadIdTypeToIndex(npad_id_type);
+    for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid_index);
+        if (!data->flag.is_assigned) {
+            continue;
+        }
+        auto& npad_entry = data->shared_memory_format->npad.npad_entry[npad_index];
+        UpdateDeviceProperties(data->aruid, npad_entry);
+    }
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetFullkeyInterfaceType() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (abstract_pad->device_type != Core::HID::NpadStyleIndex::Fullkey) {
+            continue;
+        }
+        if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+            // Abort
+            continue;
+        }
+        return abstract_pad->interface_type;
+    }
+
+    return Core::HID::NpadInterfaceType::None;
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetInterfaceType() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (!abstract_pad->disabled_feature_set.has_identification_code) {
+            continue;
+        }
+        if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+            // Abort
+            continue;
+        }
+        return abstract_pad->interface_type;
+    }
+    return Core::HID::NpadInterfaceType::None;
+}
+
+Core::HID::NpadStyleSet NpadAbstractPropertiesHandler::GetStyleSet(u64 aruid) {
+    // TODO
+    return Core::HID::NpadStyleSet::None;
+}
+
+std::size_t NpadAbstractPropertiesHandler::GetAbstractedPadsWithStyleTag(
+    std::span<IAbstractedPad*> list, Core::HID::NpadStyleTag style) {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    if (count == 0) {
+        return count;
+    }
+
+    bool is_supported_style_set{};
+    const auto result = applet_resource_holder->shared_npad_resource->IsSupportedNpadStyleSet(
+        is_supported_style_set, applet_resource_holder->applet_resource->GetActiveAruid());
+
+    if (!is_supported_style_set || result.IsError()) {
+        for (std::size_t i = 0; i < count; i++) {
+            // TODO
+        }
+        return count;
+    }
+
+    std::size_t filtered_count{};
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        const bool is_enabled = true;
+        if (is_enabled) {
+            list[filtered_count] = abstract_pad;
+            filtered_count++;
+        }
+    }
+
+    return filtered_count;
+}
+
+std::size_t NpadAbstractPropertiesHandler::GetAbstractedPads(std::span<IAbstractedPad*> list) {
+    Core::HID::NpadStyleTag style{
+        GetStyleSet(applet_resource_holder->applet_resource->GetActiveAruid())};
+    return GetAbstractedPadsWithStyleTag(list, style);
+}
+
+AppletFooterUiType NpadAbstractPropertiesHandler::GetAppletFooterUiType() {
+    return applet_ui_type.footer;
+}
+
+AppletDetailedUiType NpadAbstractPropertiesHandler::GetAppletDetailedUiType() {
+    return applet_ui_type;
+}
+
+void NpadAbstractPropertiesHandler::UpdateDeviceProperties(u64 aruid,
+                                                           NpadSharedMemoryEntry& internal_state) {
+    // TODO
+}
+
+Core::HID::NpadInterfaceType NpadAbstractPropertiesHandler::GetNpadInterfaceType() {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (abstract_pad->interface_type >= Core::HID::NpadInterfaceType::Embedded) {
+            // Abort
+            continue;
+        }
+        return abstract_pad->interface_type;
+    }
+
+    return Core::HID::NpadInterfaceType::None;
+}
+
+Result NpadAbstractPropertiesHandler::GetNpadFullKeyGripColor(
+    Core::HID::NpadColor& main_color, Core::HID::NpadColor& sub_color) const {
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    if (applet_ui_type.footer != AppletFooterUiType::SwitchProController) {
+        return ResultNpadIsNotProController;
+    }
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        return ResultSuccess;
+    }
+
+    return ResultNpadIsNotProController;
+}
+
+void NpadAbstractPropertiesHandler::GetNpadLeftRightInterfaceType(
+    Core::HID::NpadInterfaceType& out_left_interface,
+    Core::HID::NpadInterfaceType& out_right_interface) const {
+    out_left_interface = Core::HID::NpadInterfaceType::None;
+    out_right_interface = Core::HID::NpadInterfaceType::None;
+
+    std::array<IAbstractedPad*, 5> abstract_pads{};
+    const std::size_t count = abstract_pad_holder->GetAbstractedPads(abstract_pads);
+
+    for (std::size_t i = 0; i < count; i++) {
+        auto* abstract_pad = abstract_pads[i];
+        if (!abstract_pad->internal_flags.is_connected) {
+            continue;
+        }
+        if (abstract_pad->assignment_style.is_external_left_assigned &&
+            abstract_pad->assignment_style.is_handheld_left_assigned) {
+            if (abstract_pad->interface_type > Core::HID::NpadInterfaceType::Embedded) {
+                // Abort
+                continue;
+            }
+            out_left_interface = abstract_pad->interface_type;
+            continue;
+        }
+        if (abstract_pad->assignment_style.is_external_right_assigned &&
+            abstract_pad->assignment_style.is_handheld_right_assigned) {
+            if (abstract_pad->interface_type > Core::HID::NpadInterfaceType::Embedded) {
+                // Abort
+                continue;
+            }
+            out_right_interface = abstract_pad->interface_type;
+            continue;
+        }
+    }
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h
new file mode 100644
index 000000000..fa6827899
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+
+namespace Service::HID {
+struct NpadSharedMemoryEntry;
+
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+
+struct ColorProperties {
+    ColorAttribute attribute;
+    Core::HID::NpadControllerColor color;
+    INSERT_PADDING_BYTES(0x4);
+};
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractPropertiesHandler final {
+public:
+    explicit NpadAbstractPropertiesHandler();
+    ~NpadAbstractPropertiesHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetNpadId(Core::HID::NpadIdType npad_id);
+
+    Core::HID::NpadIdType GetNpadId() const;
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    Result ActivateNpadUnknown0x88(u64 aruid);
+
+    void UpdateDeviceType();
+    void UpdateDeviceColor();
+    void UpdateFooterAttributes();
+    void UpdateAllDeviceProperties();
+
+    Core::HID::NpadInterfaceType GetFullkeyInterfaceType();
+    Core::HID::NpadInterfaceType GetInterfaceType();
+
+    Core::HID::NpadStyleSet GetStyleSet(u64 aruid);
+    std::size_t GetAbstractedPadsWithStyleTag(std::span<IAbstractedPad*> list,
+                                              Core::HID::NpadStyleTag style);
+    std::size_t GetAbstractedPads(std::span<IAbstractedPad*> list);
+
+    AppletFooterUiType GetAppletFooterUiType();
+
+    AppletDetailedUiType GetAppletDetailedUiType();
+
+    void UpdateDeviceProperties(u64 aruid, NpadSharedMemoryEntry& internal_state);
+
+    Core::HID::NpadInterfaceType GetNpadInterfaceType();
+
+    Result GetNpadFullKeyGripColor(Core::HID::NpadColor& main_color,
+                                   Core::HID::NpadColor& sub_color) const;
+
+    void GetNpadLeftRightInterfaceType(Core::HID::NpadInterfaceType& param_2,
+                                       Core::HID::NpadInterfaceType& param_3) const;
+
+private:
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    Core::HID::NpadIdType npad_id_type{Core::HID::NpadIdType::Invalid};
+    s32 ref_counter{};
+    Core::HID::DeviceIndex device_type{};
+    AppletDetailedUiType applet_ui_type{};
+    AppletFooterUiAttributes applet_ui_attributes{};
+    bool is_vertical{};
+    bool is_horizontal{};
+    bool use_plus{};
+    bool use_minus{};
+    bool has_directional_buttons{};
+    ColorProperties fullkey_color{};
+    ColorProperties left_color{};
+    ColorProperties right_color{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp
new file mode 100644
index 000000000..6d759298e
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp
@@ -0,0 +1,154 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/shared_memory_format.h"
+
+namespace Service::HID {
+
+NpadAbstractSixAxisHandler::NpadAbstractSixAxisHandler() {}
+
+NpadAbstractSixAxisHandler::~NpadAbstractSixAxisHandler() = default;
+
+void NpadAbstractSixAxisHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractSixAxisHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractSixAxisHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+void NpadAbstractSixAxisHandler::SetSixaxisResource(SixAxisResource* resource) {
+    six_axis_resource = resource;
+}
+
+Result NpadAbstractSixAxisHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+u64 NpadAbstractSixAxisHandler::IsFirmwareUpdateAvailable() {
+    // TODO
+    return false;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState() {
+    Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    for (std::size_t i = 0; i < AruidIndexMax; i++) {
+        auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
+        if (data->flag.is_assigned) {
+            continue;
+        }
+        auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+        UpdateSixaxisInternalState(npad_entry, data->aruid,
+                                   data->flag.enable_six_axis_sensor.As<bool>());
+    }
+    return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState(u64 aruid) {
+    Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
+    auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+    if (data == nullptr) {
+        return ResultSuccess;
+    }
+    auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
+    UpdateSixaxisInternalState(npad_entry, data->aruid,
+                               data->flag.enable_six_axis_sensor.As<bool>());
+    return ResultSuccess;
+}
+
+Result NpadAbstractSixAxisHandler::UpdateSixAxisState2(u64 aruid) {
+    const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId());
+    AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid);
+    if (aruid_data == nullptr) {
+        return ResultSuccess;
+    }
+    auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index];
+    UpdateSixaxisInternalState(npad_internal_state, aruid,
+                               aruid_data->flag.enable_six_axis_sensor.As<bool>());
+    return ResultSuccess;
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisInternalState(NpadSharedMemoryEntry& npad_entry,
+                                                            u64 aruid, bool is_sensor_enabled) {
+    const Core::HID::NpadStyleTag style_tag{properties_handler->GetStyleSet(aruid)};
+
+    if (!style_tag.palma) {
+        UpdateSixaxisFullkeyLifo(style_tag, npad_entry.internal_state.sixaxis_fullkey_lifo,
+                                 is_sensor_enabled);
+    } else {
+        UpdateSixAxisPalmaLifo(style_tag, npad_entry.internal_state.sixaxis_fullkey_lifo,
+                               is_sensor_enabled);
+    }
+    UpdateSixaxisHandheldLifo(style_tag, npad_entry.internal_state.sixaxis_handheld_lifo,
+                              is_sensor_enabled);
+    UpdateSixaxisDualLifo(style_tag, npad_entry.internal_state.sixaxis_dual_left_lifo,
+                          is_sensor_enabled);
+    UpdateSixaxisDualLifo(style_tag, npad_entry.internal_state.sixaxis_dual_right_lifo,
+                          is_sensor_enabled);
+    UpdateSixaxisLeftLifo(style_tag, npad_entry.internal_state.sixaxis_left_lifo,
+                          is_sensor_enabled);
+    UpdateSixaxisRightLifo(style_tag, npad_entry.internal_state.sixaxis_right_lifo,
+                           is_sensor_enabled);
+    // TODO: Set sixaxis properties
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+                                                          NpadSixAxisSensorLifo& sensor_lifo,
+                                                          bool is_sensor_enabled) {
+    // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixAxisPalmaLifo(Core::HID::NpadStyleTag style_tag,
+                                                        NpadSixAxisSensorLifo& sensor_lifo,
+                                                        bool is_sensor_enabled) {
+    // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisHandheldLifo(Core::HID::NpadStyleTag style_tag,
+                                                           NpadSixAxisSensorLifo& sensor_lifo,
+                                                           bool is_sensor_enabled) {
+    // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisDualLifo(Core::HID::NpadStyleTag style_tag,
+                                                       NpadSixAxisSensorLifo& sensor_lifo,
+                                                       bool is_sensor_enabled) {
+    // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisLeftLifo(Core::HID::NpadStyleTag style_tag,
+                                                       NpadSixAxisSensorLifo& sensor_lifo,
+                                                       bool is_sensor_enabled) {
+    // TODO
+}
+
+void NpadAbstractSixAxisHandler::UpdateSixaxisRightLifo(Core::HID::NpadStyleTag style_tag,
+                                                        NpadSixAxisSensorLifo& sensor_lifo,
+                                                        bool is_sensor_enabled) {
+    // TODO
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h
new file mode 100644
index 000000000..9c20459e9
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+class SixAxisResource;
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+struct NpadSixAxisSensorLifo;
+
+/// Handles Npad request from HID interfaces
+class NpadAbstractSixAxisHandler final {
+public:
+    explicit NpadAbstractSixAxisHandler();
+    ~NpadAbstractSixAxisHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+    void SetSixaxisResource(SixAxisResource* resource);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    u64 IsFirmwareUpdateAvailable();
+
+    Result UpdateSixAxisState();
+    Result UpdateSixAxisState(u64 aruid);
+    Result UpdateSixAxisState2(u64 aruid);
+
+private:
+    void UpdateSixaxisInternalState(NpadSharedMemoryEntry& npad_entry, u64 aruid,
+                                    bool is_sensor_enabled);
+    void UpdateSixaxisFullkeyLifo(Core::HID::NpadStyleTag style_tag,
+                                  NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+    void UpdateSixAxisPalmaLifo(Core::HID::NpadStyleTag style_tag,
+                                NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+    void UpdateSixaxisHandheldLifo(Core::HID::NpadStyleTag style_tag,
+                                   NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+    void UpdateSixaxisDualLifo(Core::HID::NpadStyleTag style_tag,
+                               NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+    void UpdateSixaxisLeftLifo(Core::HID::NpadStyleTag style_tag,
+                               NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+    void UpdateSixaxisRightLifo(Core::HID::NpadStyleTag style_tag,
+                                NpadSixAxisSensorLifo& sensor_lifo, bool is_sensor_enabled);
+
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+    SixAxisResource* six_axis_resource{nullptr};
+
+    s32 ref_counter{};
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp
new file mode 100644
index 000000000..a00d6c9de
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/hid_util.h"
+#include "hid_core/resources/abstracted_pad/abstract_pad_holder.h"
+#include "hid_core/resources/abstracted_pad/abstract_properties_handler.h"
+#include "hid_core/resources/abstracted_pad/abstract_vibration_handler.h"
+#include "hid_core/resources/applet_resource.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+#include "hid_core/resources/vibration/vibration_device.h"
+
+namespace Service::HID {
+
+NpadAbstractVibrationHandler::NpadAbstractVibrationHandler() {}
+
+NpadAbstractVibrationHandler::~NpadAbstractVibrationHandler() = default;
+
+void NpadAbstractVibrationHandler::SetAbstractPadHolder(NpadAbstractedPadHolder* holder) {
+    abstract_pad_holder = holder;
+}
+
+void NpadAbstractVibrationHandler::SetAppletResource(AppletResourceHolder* applet_resource) {
+    applet_resource_holder = applet_resource;
+}
+
+void NpadAbstractVibrationHandler::SetPropertiesHandler(NpadAbstractPropertiesHandler* handler) {
+    properties_handler = handler;
+}
+
+void NpadAbstractVibrationHandler::SetN64Vibration(NpadN64VibrationDevice* n64_device) {
+    n64_vibration_device = n64_device;
+}
+
+void NpadAbstractVibrationHandler::SetVibration(std::span<NpadVibrationDevice*> device) {
+    for (std::size_t i = 0; i < device.size() && i < vibration_device.size(); i++) {
+        vibration_device[i] = device[i];
+    }
+}
+
+void NpadAbstractVibrationHandler::SetGcVibration(NpadGcVibrationDevice* gc_device) {
+    gc_vibration_device = gc_device;
+}
+
+Result NpadAbstractVibrationHandler::IncrementRefCounter() {
+    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
+        return ResultNpadHandlerOverflow;
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadAbstractVibrationHandler::DecrementRefCounter() {
+    if (ref_counter == 0) {
+        return ResultNpadHandlerNotInitialized;
+    }
+    ref_counter--;
+    return ResultSuccess;
+}
+
+void NpadAbstractVibrationHandler::UpdateVibrationState() {
+    const bool is_handheld_hid_enabled =
+        applet_resource_holder->handheld_config->is_handheld_hid_enabled;
+    const bool is_force_handheld_style_vibration =
+        applet_resource_holder->handheld_config->is_force_handheld_style_vibration;
+
+    if (!is_handheld_hid_enabled && is_force_handheld_style_vibration) {
+        // TODO
+    }
+}
+} // namespace Service::HID
diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h
new file mode 100644
index 000000000..aeb07ce86
--- /dev/null
+++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+struct AppletResourceHolder;
+class NpadAbstractedPadHolder;
+class NpadAbstractPropertiesHandler;
+class NpadGcVibrationDevice;
+class NpadVibrationDevice;
+class NpadN64VibrationDevice;
+class NpadVibration;
+
+/// Keeps track of battery levels and updates npad battery shared memory values
+class NpadAbstractVibrationHandler final {
+public:
+    explicit NpadAbstractVibrationHandler();
+    ~NpadAbstractVibrationHandler();
+
+    void SetAbstractPadHolder(NpadAbstractedPadHolder* holder);
+    void SetAppletResource(AppletResourceHolder* applet_resource);
+    void SetPropertiesHandler(NpadAbstractPropertiesHandler* handler);
+
+    void SetN64Vibration(NpadN64VibrationDevice* n64_device);
+    void SetVibration(std::span<NpadVibrationDevice*> device);
+    void SetGcVibration(NpadGcVibrationDevice* gc_device);
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    void UpdateVibrationState();
+
+private:
+    AppletResourceHolder* applet_resource_holder{nullptr};
+    NpadAbstractedPadHolder* abstract_pad_holder{nullptr};
+    NpadAbstractPropertiesHandler* properties_handler{nullptr};
+
+    NpadN64VibrationDevice* n64_vibration_device{nullptr};
+    std::array<NpadVibrationDevice*, 2> vibration_device{};
+    NpadGcVibrationDevice* gc_vibration_device{nullptr};
+    NpadVibration* vibration_handler{nullptr};
+    s32 ref_counter{};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp
index 1f8a0f8ab..97537a2e2 100644
--- a/src/hid_core/resources/npad/npad.cpp
+++ b/src/hid_core/resources/npad/npad.cpp
@@ -193,7 +193,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) {
     case Core::HID::NpadStyleIndex::None:
         ASSERT(false);
         break;
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
         shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
         shared_memory->fullkey_color.fullkey = body_colors.fullkey;
         shared_memory->battery_level_dual = battery_level.dual.battery_level;
@@ -491,7 +491,7 @@ void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
             case Core::HID::NpadStyleIndex::None:
                 ASSERT(false);
                 break;
-            case Core::HID::NpadStyleIndex::ProController:
+            case Core::HID::NpadStyleIndex::Fullkey:
             case Core::HID::NpadStyleIndex::NES:
             case Core::HID::NpadStyleIndex::SNES:
             case Core::HID::NpadStyleIndex::N64:
@@ -1292,7 +1292,7 @@ Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
     u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
     auto& controller = GetControllerFromHandle(aruid, sixaxis_handle);
     switch (sixaxis_handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Pokeball:
         return controller.shared_memory->sixaxis_fullkey_properties;
     case Core::HID::NpadStyleIndex::Handheld:
@@ -1315,7 +1315,7 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties(
     u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
     const auto& controller = GetControllerFromHandle(aruid, sixaxis_handle);
     switch (sixaxis_handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Pokeball:
         return controller.shared_memory->sixaxis_fullkey_properties;
     case Core::HID::NpadStyleIndex::Handheld:
diff --git a/src/hid_core/resources/npad/npad_data.cpp b/src/hid_core/resources/npad/npad_data.cpp
index c7e9760cb..29ad5cb08 100644
--- a/src/hid_core/resources/npad/npad_data.cpp
+++ b/src/hid_core/resources/npad/npad_data.cpp
@@ -151,7 +151,7 @@ Core::HID::NpadStyleSet NPadData::GetSupportedNpadStyleSet() const {
 bool NPadData::IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index) const {
     Core::HID::NpadStyleTag style = {supported_npad_style_set};
     switch (style_index) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
         return style.fullkey.As<bool>();
     case Core::HID::NpadStyleIndex::Handheld:
         return style.handheld.As<bool>();
diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h
index a02f9cf16..074dd40cf 100644
--- a/src/hid_core/resources/npad/npad_types.h
+++ b/src/hid_core/resources/npad/npad_types.h
@@ -252,4 +252,103 @@ enum class NpadLagerType : u32 {
     U,
 };
 
+// nn::hidtypes::FeatureType
+struct FeatureType {
+    union {
+        u64 raw{};
+        BitField<0, 1, u64> has_left_analog_stick;
+        BitField<1, 1, u64> has_right_analog_stick;
+        BitField<2, 1, u64> has_left_joy_six_axis_sensor;
+        BitField<3, 1, u64> has_right_joy_six_axis_sensor;
+        BitField<4, 1, u64> has_fullkey_joy_six_axis_sensor;
+        BitField<5, 1, u64> has_left_lra_vibration_device;
+        BitField<6, 1, u64> has_right_lra_vibration_device;
+        BitField<7, 1, u64> has_gc_vibration_device;
+        BitField<8, 1, u64> has_erm_vibration_device;
+        BitField<9, 1, u64> has_left_joy_rail_bus;
+        BitField<10, 1, u64> has_right_joy_rail_bus;
+        BitField<11, 1, u64> has_internal_bus;
+        BitField<12, 1, u64> is_palma;
+        BitField<13, 1, u64> has_nfc;
+        BitField<14, 1, u64> has_ir_sensor;
+        BitField<15, 1, u64> is_analog_stick_calibration_supported;
+        BitField<16, 1, u64> is_six_axis_Sensor_user_calibration_supported;
+        BitField<17, 1, u64> has_left_right_joy_battery;
+        BitField<18, 1, u64> has_fullkey_battery;
+        BitField<19, 1, u64> is_disconnect_controller_if_battery_none;
+        BitField<20, 1, u64> has_controller_color;
+        BitField<21, 1, u64> has_grip_color;
+        BitField<22, 1, u64> has_identification_code;
+        BitField<23, 1, u64> has_bluetooth_address;
+        BitField<24, 1, u64> has_mcu;
+        BitField<25, 1, u64> has_notification_led;
+        BitField<26, 1, u64> has_directional_buttons;
+        BitField<27, 1, u64> has_indicator_led;
+        BitField<28, 1, u64> is_button_config_embedded_supported;
+        BitField<29, 1, u64> is_button_config_full_supported;
+        BitField<30, 1, u64> is_button_config_left_supported;
+        BitField<31, 1, u64> is_button_config_right_supported;
+        BitField<32, 1, u64> is_usb_hid_device;
+        BitField<33, 1, u64> is_kuina_device;
+        BitField<34, 1, u64> is_direct_usb_to_bt_switching_device;
+        BitField<35, 1, u64> is_normalize_analog_stick_with_inner_cross;
+    };
+};
+static_assert(sizeof(FeatureType) == 8, "FeatureType is an invalid size");
+
+// This is nn::hid::AssignmentStyle
+struct AssignmentStyle {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> is_external_assigned;
+        BitField<1, 1, u32> is_external_left_assigned;
+        BitField<2, 1, u32> is_external_right_assigned;
+        BitField<3, 1, u32> is_handheld_assigned;
+        BitField<4, 1, u32> is_handheld_left_assigned;
+        BitField<5, 1, u32> is_handheld_right_assigned;
+    };
+};
+static_assert(sizeof(AssignmentStyle) == 4, "AssignmentStyle is an invalid size");
+
+// This is nn::hid::server::IAbstractedPad::InternalFlags
+struct InternalFlags {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> is_bound;
+        BitField<1, 1, u32> is_connected;
+        BitField<2, 1, u32> is_battery_low_ovln_required;
+        BitField<3, 1, u32> is_battery_low_ovln_delay_required;
+        BitField<4, 1, u32> is_sample_recieved;
+        BitField<5, 1, u32> is_virtual_input;
+        BitField<6, 1, u32> is_wired;
+        BitField<8, 1, u32> use_center_clamp;
+        BitField<9, 1, u32> has_virtual_six_axis_sensor_acceleration;
+        BitField<10, 1, u32> has_virtual_six_axis_sensor_angle;
+        BitField<11, 1, u32> is_debug_pad;
+    };
+};
+static_assert(sizeof(InternalFlags) == 4, "InternalFlags is an invalid size");
+
+/// This is nn::hid::server::IAbstractedPad
+struct IAbstractedPad {
+    InternalFlags internal_flags;
+    u64 controller_id;
+    u32 controller_number;
+    u64 low_battery_display_delay_time;
+    u64 low_battery_display_delay_interval;
+    FeatureType feature_set;
+    FeatureType disabled_feature_set;
+    AssignmentStyle assignment_style;
+    Core::HID::NpadStyleIndex device_type;
+    Core::HID::NpadInterfaceType interface_type;
+    Core::HID::NpadPowerInfo power_info;
+    u32 pad_state;
+    u32 button_mask;
+    u32 system_button_mask;
+    u8 indicator;
+    std::vector<f32> virtual_six_axis_sensor_acceleration;
+    std::vector<f32> virtual_six_axis_sensor_angle;
+    u64 xcd_handle;
+    u64 color;
+};
 } // namespace Service::HID
diff --git a/src/hid_core/resources/npad/npad_vibration.cpp b/src/hid_core/resources/npad/npad_vibration.cpp
new file mode 100644
index 000000000..3bdd55dec
--- /dev/null
+++ b/src/hid_core/resources/npad/npad_vibration.cpp
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+
+namespace Service::HID {
+
+NpadVibration::NpadVibration() {}
+
+NpadVibration::~NpadVibration() = default;
+
+Result NpadVibration::Activate() {
+    std::scoped_lock lock{mutex};
+
+    const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+    // if (master_volume < 0.0f || master_volume > 1.0f) {
+    //     return ResultVibrationStrenghtOutOfRange;
+    // }
+
+    volume = master_volume;
+    return ResultSuccess;
+}
+
+Result NpadVibration::Deactivate() {
+    return ResultSuccess;
+}
+
+Result NpadVibration::SetVibrationMasterVolume(f32 master_volume) {
+    std::scoped_lock lock{mutex};
+
+    if (master_volume < 0.0f && master_volume > 1.0f) {
+        return ResultVibrationStrenghtOutOfRange;
+    }
+
+    volume = master_volume;
+    // nn::settings::system::SetVibrationMasterVolume(master_volume);
+
+    return ResultSuccess;
+}
+
+Result NpadVibration::GetVibrationVolume(f32& out_volume) const {
+    std::scoped_lock lock{mutex};
+    out_volume = volume;
+    return ResultSuccess;
+}
+
+Result NpadVibration::GetVibrationMasterVolume(f32& out_volume) const {
+    std::scoped_lock lock{mutex};
+
+    const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+    // if (master_volume < 0.0f || master_volume > 1.0f) {
+    //     return ResultVibrationStrenghtOutOfRange;
+    // }
+
+    out_volume = master_volume;
+    return ResultSuccess;
+}
+
+Result NpadVibration::BeginPermitVibrationSession(u64 aruid) {
+    std::scoped_lock lock{mutex};
+    session_aruid = aruid;
+    volume = 1.0;
+    return ResultSuccess;
+}
+
+Result NpadVibration::EndPermitVibrationSession() {
+    std::scoped_lock lock{mutex};
+
+    const f32 master_volume = 1.0f; // nn::settings::system::GetVibrationMasterVolume();
+    // if (master_volume < 0.0f || master_volume > 1.0f) {
+    //     return ResultVibrationStrenghtOutOfRange;
+    // }
+
+    volume = master_volume;
+    session_aruid = 0;
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/npad/npad_vibration.h b/src/hid_core/resources/npad/npad_vibration.h
new file mode 100644
index 000000000..0748aeffc
--- /dev/null
+++ b/src/hid_core/resources/npad/npad_vibration.h
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Service::HID {
+
+class NpadVibration final {
+public:
+    explicit NpadVibration();
+    ~NpadVibration();
+
+    Result Activate();
+    Result Deactivate();
+
+    Result SetVibrationMasterVolume(f32 master_volume);
+    Result GetVibrationVolume(f32& out_volume) const;
+    Result GetVibrationMasterVolume(f32& out_volume) const;
+
+    Result BeginPermitVibrationSession(u64 aruid);
+    Result EndPermitVibrationSession();
+
+private:
+    f32 volume{};
+    u64 session_aruid{};
+    mutable std::mutex mutex;
+};
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/six_axis/six_axis.cpp b/src/hid_core/resources/six_axis/six_axis.cpp
index 8a9677c50..da12d2d5a 100644
--- a/src/hid_core/resources/six_axis/six_axis.cpp
+++ b/src/hid_core/resources/six_axis/six_axis.cpp
@@ -114,7 +114,7 @@ void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
         case Core::HID::NpadStyleIndex::None:
             ASSERT(false);
             break;
-        case Core::HID::NpadStyleIndex::ProController:
+        case Core::HID::NpadStyleIndex::Fullkey:
             set_motion_state(sixaxis_fullkey_state, motion_state[0]);
             break;
         case Core::HID::NpadStyleIndex::Handheld:
@@ -345,7 +345,7 @@ SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
     const Core::HID::SixAxisSensorHandle& sixaxis_handle) {
     auto& controller = GetControllerFromHandle(sixaxis_handle);
     switch (sixaxis_handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Pokeball:
         return controller.sixaxis_fullkey;
     case Core::HID::NpadStyleIndex::Handheld:
@@ -368,7 +368,7 @@ const SixAxis::SixaxisParameters& SixAxis::GetSixaxisState(
     const Core::HID::SixAxisSensorHandle& sixaxis_handle) const {
     const auto& controller = GetControllerFromHandle(sixaxis_handle);
     switch (sixaxis_handle.npad_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Pokeball:
         return controller.sixaxis_fullkey;
     case Core::HID::NpadStyleIndex::Handheld:
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.cpp b/src/hid_core/resources/vibration/gc_vibration_device.cpp
new file mode 100644
index 000000000..f01f81b9a
--- /dev/null
+++ b/src/hid_core/resources/vibration/gc_vibration_device.cpp
@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/gc_vibration_device.h"
+
+namespace Service::HID {
+
+NpadGcVibrationDevice::NpadGcVibrationDevice() {}
+
+Result NpadGcVibrationDevice::IncrementRefCounter() {
+    if (ref_counter == 0 && is_mounted) {
+        f32 volume = 1.0f;
+        const auto result = vibration_handler->GetVibrationVolume(volume);
+        if (result.IsSuccess()) {
+            // TODO: SendVibrationGcErmCommand
+        }
+    }
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::DecrementRefCounter() {
+    if (ref_counter == 1 && !is_mounted) {
+        f32 volume = 1.0f;
+        const auto result = vibration_handler->GetVibrationVolume(volume);
+        if (result.IsSuccess()) {
+            // TODO: SendVibrationGcErmCommand
+        }
+    }
+
+    if (ref_counter > 0) {
+        ref_counter--;
+    }
+
+    return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command) {
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume == 0.0) {
+        command = Core::HID::VibrationGcErmCommand::Stop;
+    } else {
+        if (command > Core::HID::VibrationGcErmCommand::StopHard) {
+            // Abort
+            return ResultSuccess;
+        }
+    }
+    // TODO: SendVibrationGcErmCommand
+    return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::GetActualVibrationGcErmCommand(
+    Core::HID::VibrationGcErmCommand& out_command) {
+    if (!is_mounted) {
+        out_command = Core::HID::VibrationGcErmCommand::Stop;
+        return ResultSuccess;
+    }
+
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume == 0.0f) {
+        out_command = Core::HID::VibrationGcErmCommand::Stop;
+        return ResultSuccess;
+    }
+
+    // TODO: GetActualVibrationGcErmCommand
+    return ResultSuccess;
+}
+
+Result NpadGcVibrationDevice::SendVibrationNotificationPattern(
+    Core::HID::VibrationGcErmCommand command) {
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume <= 0.0f) {
+        command = Core::HID::VibrationGcErmCommand::Stop;
+    }
+    if (command > Core::HID::VibrationGcErmCommand::StopHard) {
+        // Abort
+        return ResultSuccess;
+    }
+
+    // TODO: SendVibrationNotificationPattern
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/gc_vibration_device.h b/src/hid_core/resources/vibration/gc_vibration_device.h
new file mode 100644
index 000000000..87abca57d
--- /dev/null
+++ b/src/hid_core/resources/vibration/gc_vibration_device.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadGcVibrationDevice final : public NpadVibrationBase {
+public:
+    explicit NpadGcVibrationDevice();
+
+    Result IncrementRefCounter() override;
+    Result DecrementRefCounter() override;
+
+    Result SendVibrationGcErmCommand(Core::HID::VibrationGcErmCommand command);
+
+    Result GetActualVibrationGcErmCommand(Core::HID::VibrationGcErmCommand& out_command);
+    Result SendVibrationNotificationPattern(Core::HID::VibrationGcErmCommand command);
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/n64_vibration_device.cpp b/src/hid_core/resources/vibration/n64_vibration_device.cpp
new file mode 100644
index 000000000..639f87abf
--- /dev/null
+++ b/src/hid_core/resources/vibration/n64_vibration_device.cpp
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/n64_vibration_device.h"
+
+namespace Service::HID {
+
+NpadN64VibrationDevice::NpadN64VibrationDevice() {}
+
+Result NpadN64VibrationDevice::IncrementRefCounter() {
+    if (ref_counter == 0 && is_mounted) {
+        f32 volume = 1.0f;
+        const auto result = vibration_handler->GetVibrationVolume(volume);
+        if (result.IsSuccess()) {
+            // TODO: SendVibrationInBool
+        }
+    }
+
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::DecrementRefCounter() {
+    if (ref_counter == 1) {
+        if (!is_mounted) {
+            ref_counter = 0;
+            if (is_mounted != false) {
+                // TODO: SendVibrationInBool
+            }
+            return ResultSuccess;
+        }
+        f32 volume = 1.0f;
+        const auto result = vibration_handler->GetVibrationVolume(volume);
+        if (result.IsSuccess()) {
+            // TODO
+        }
+    }
+
+    if (ref_counter > 0) {
+        ref_counter--;
+    }
+
+    return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::SendValueInBool(bool is_vibrating) {
+    if (ref_counter < 1) {
+        return ResultVibrationNotInitialized;
+    }
+    if (is_mounted) {
+        f32 volume = 1.0f;
+        const auto result = vibration_handler->GetVibrationVolume(volume);
+        if (result.IsError()) {
+            return result;
+        }
+        // TODO: SendVibrationInBool
+    }
+    return ResultSuccess;
+}
+
+Result NpadN64VibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u32 pattern) {
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume <= 0.0) {
+        pattern = 0;
+    }
+    // TODO: SendVibrationNotificationPattern
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/n64_vibration_device.h b/src/hid_core/resources/vibration/n64_vibration_device.h
new file mode 100644
index 000000000..54e6efc1a
--- /dev/null
+++ b/src/hid_core/resources/vibration/n64_vibration_device.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadN64VibrationDevice final : public NpadVibrationBase {
+public:
+    explicit NpadN64VibrationDevice();
+
+    Result IncrementRefCounter() override;
+    Result DecrementRefCounter() override;
+
+    Result SendValueInBool(bool is_vibrating);
+    Result SendVibrationNotificationPattern(u32 pattern);
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_base.cpp b/src/hid_core/resources/vibration/vibration_base.cpp
new file mode 100644
index 000000000..350f349c2
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_base.cpp
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+
+NpadVibrationBase::NpadVibrationBase() {}
+
+Result NpadVibrationBase::IncrementRefCounter() {
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadVibrationBase::DecrementRefCounter() {
+    if (ref_counter > 0) {
+        ref_counter--;
+    }
+
+    return ResultSuccess;
+}
+
+bool NpadVibrationBase::IsVibrationMounted() const {
+    return is_mounted;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_base.h b/src/hid_core/resources/vibration/vibration_base.h
new file mode 100644
index 000000000..c6c5fc4d9
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_base.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadVibrationBase {
+public:
+    explicit NpadVibrationBase();
+
+    virtual Result IncrementRefCounter();
+    virtual Result DecrementRefCounter();
+
+    bool IsVibrationMounted() const;
+
+protected:
+    u64 xcd_handle{};
+    s32 ref_counter{};
+    bool is_mounted{};
+    NpadVibration* vibration_handler{nullptr};
+};
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_device.cpp b/src/hid_core/resources/vibration/vibration_device.cpp
new file mode 100644
index 000000000..888c3a7ed
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_device.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "hid_core/hid_result.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/npad/npad_vibration.h"
+#include "hid_core/resources/vibration/vibration_device.h"
+
+namespace Service::HID {
+
+NpadVibrationDevice::NpadVibrationDevice() {}
+
+Result NpadVibrationDevice::IncrementRefCounter() {
+    ref_counter++;
+    return ResultSuccess;
+}
+
+Result NpadVibrationDevice::DecrementRefCounter() {
+    if (ref_counter > 0) {
+        ref_counter--;
+    }
+
+    return ResultSuccess;
+}
+
+Result NpadVibrationDevice::SendVibrationValue(const Core::HID::VibrationValue& value) {
+    if (ref_counter == 0) {
+        return ResultVibrationNotInitialized;
+    }
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume <= 0.0f) {
+        // TODO: SendVibrationValue
+        return ResultSuccess;
+    }
+
+    Core::HID::VibrationValue vibration_value = value;
+    vibration_value.high_amplitude *= volume;
+    vibration_value.low_amplitude *= volume;
+
+    // TODO: SendVibrationValue
+    return ResultSuccess;
+}
+
+Result NpadVibrationDevice::SendVibrationNotificationPattern([[maybe_unused]] u32 pattern) {
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+
+    f32 volume = 1.0f;
+    const auto result = vibration_handler->GetVibrationVolume(volume);
+    if (result.IsError()) {
+        return result;
+    }
+    if (volume <= 0.0) {
+        pattern = 0;
+    }
+
+    // return xcd_handle->SendVibrationNotificationPattern(pattern);
+    return ResultSuccess;
+}
+
+Result NpadVibrationDevice::GetActualVibrationValue(Core::HID::VibrationValue& out_value) {
+    if (ref_counter < 1) {
+        return ResultVibrationNotInitialized;
+    }
+
+    out_value = Core::HID::DEFAULT_VIBRATION_VALUE;
+    if (!is_mounted) {
+        return ResultSuccess;
+    }
+
+    // TODO: SendVibrationValue
+    return ResultSuccess;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/vibration/vibration_device.h b/src/hid_core/resources/vibration/vibration_device.h
new file mode 100644
index 000000000..3574ad60b
--- /dev/null
+++ b/src/hid_core/resources/vibration/vibration_device.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/npad/npad_types.h"
+#include "hid_core/resources/vibration/vibration_base.h"
+
+namespace Service::HID {
+class NpadVibration;
+
+/// Handles Npad request from HID interfaces
+class NpadVibrationDevice final : public NpadVibrationBase {
+public:
+    explicit NpadVibrationDevice();
+
+    Result IncrementRefCounter();
+    Result DecrementRefCounter();
+
+    Result SendVibrationValue(const Core::HID::VibrationValue& value);
+    Result SendVibrationNotificationPattern(u32 pattern);
+
+    Result GetActualVibrationValue(Core::HID::VibrationValue& out_value);
+
+private:
+    u32 device_index{};
+};
+
+} // namespace Service::HID
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 8b340ee6c..48ce860ad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -41,7 +41,7 @@ void UpdateController(Core::HID::EmulatedController* controller,
 bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type,
                             Core::Frontend::ControllerParameters parameters) {
     switch (controller_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
         return parameters.allow_pro_controller;
     case Core::HID::NpadStyleIndex::JoyconDual:
         return parameters.allow_dual_joycons;
@@ -462,7 +462,7 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
     };
 
     if (npad_style_set.fullkey == 1) {
-        add_item(Core::HID::NpadStyleIndex::ProController, tr("Pro Controller"));
+        add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
     }
 
     if (npad_style_set.joycon_dual == 1) {
@@ -519,7 +519,7 @@ Core::HID::NpadStyleIndex QtControllerSelectorDialog::GetControllerTypeFromIndex
                                  [index](const auto& pair) { return pair.first == index; });
 
     if (it == pairs.end()) {
-        return Core::HID::NpadStyleIndex::ProController;
+        return Core::HID::NpadStyleIndex::Fullkey;
     }
 
     return it->second;
@@ -549,7 +549,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
     const QString stylesheet = [this, player_index] {
         switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
                                            player_index)) {
-        case Core::HID::NpadStyleIndex::ProController:
+        case Core::HID::NpadStyleIndex::Fullkey:
         case Core::HID::NpadStyleIndex::GameCube:
             return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
         case Core::HID::NpadStyleIndex::JoyconDual:
diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp
index bbe17c35e..ac81ace9e 100644
--- a/src/yuzu/applets/qt_software_keyboard.cpp
+++ b/src/yuzu/applets/qt_software_keyboard.cpp
@@ -832,7 +832,7 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
     }();
 
     switch (controller_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::GameCube:
         ui->icon_controller->setStyleSheet(
             QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme));
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index f3552191a..5dac9f1e7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1094,7 +1094,7 @@ void ConfigureInputPlayer::SetConnectableControllers() {
     };
 
     if (npad_style_set.fullkey == 1) {
-        add_item(Core::HID::NpadStyleIndex::ProController, tr("Pro Controller"));
+        add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
     }
 
     if (npad_style_set.joycon_dual == 1) {
@@ -1149,7 +1149,7 @@ Core::HID::NpadStyleIndex ConfigureInputPlayer::GetControllerTypeFromIndex(int i
                      [index](const auto& pair) { return pair.first == index; });
 
     if (it == index_controller_type_pairs.end()) {
-        return Core::HID::NpadStyleIndex::ProController;
+        return Core::HID::NpadStyleIndex::Fullkey;
     }
 
     return it->second;
@@ -1178,7 +1178,7 @@ void ConfigureInputPlayer::UpdateInputDevices() {
 void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Core::HID::NpadStyleIndex::ProController;
+        layout = Core::HID::NpadStyleIndex::Fullkey;
     }
 
     // List of all the widgets that will be hidden by any of the following layouts that need
@@ -1206,7 +1206,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
 
     std::vector<QWidget*> layout_hidden;
     switch (layout) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::Handheld:
         layout_hidden = {
             ui->buttonShoulderButtonsSLSRLeft,
@@ -1254,7 +1254,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
 void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Core::HID::NpadStyleIndex::ProController;
+        layout = Core::HID::NpadStyleIndex::Fullkey;
     }
 
     // List of all the widgets that will be disabled by any of the following layouts that need
@@ -1271,7 +1271,7 @@ void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
 
     std::vector<QWidget*> layout_disable;
     switch (layout) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::JoyconLeft:
@@ -1304,7 +1304,7 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
 
     // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
     switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::JoyconLeft:
     case Core::HID::NpadStyleIndex::Handheld:
         // Show "Motion 1" and hide "Motion 2".
@@ -1333,11 +1333,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
 void ConfigureInputPlayer::UpdateControllerButtonNames() {
     auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
     if (debug) {
-        layout = Core::HID::NpadStyleIndex::ProController;
+        layout = Core::HID::NpadStyleIndex::Fullkey;
     }
 
     switch (layout) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::JoyconLeft:
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 19fdca7d3..8f91f5e92 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -244,7 +244,7 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {
     case Core::HID::NpadStyleIndex::GameCube:
         DrawGCController(p, center);
         break;
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     default:
         DrawProController(p, center);
         break;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4f4c75f5c..fd5342537 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -3988,7 +3988,7 @@ void GMainWindow::OnToggleDockedMode() {
                              tr("Handheld controller can't be used on docked mode. Pro "
                                 "controller will be selected."));
         handheld->Disconnect();
-        player_1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+        player_1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
         player_1->Connect();
         controller_dialog->refreshConfiguration();
     }
diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp
index 2690b075d..0dbfca243 100644
--- a/src/yuzu/util/controller_navigation.cpp
+++ b/src/yuzu/util/controller_navigation.cpp
@@ -66,7 +66,7 @@ void ControllerNavigation::ControllerUpdateButton() {
     }
 
     switch (controller_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::GameCube:
@@ -116,7 +116,7 @@ void ControllerNavigation::ControllerUpdateStick() {
     }
 
     switch (controller_type) {
-    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::Fullkey:
     case Core::HID::NpadStyleIndex::JoyconDual:
     case Core::HID::NpadStyleIndex::Handheld:
     case Core::HID::NpadStyleIndex::GameCube:

From d940974789b1b8ff473440883d8c506a275b9b3b Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Fri, 12 Jan 2024 09:35:08 -0500
Subject: [PATCH 22/33] audio: fetch process object from handle table

---
 src/audio_core/device/device_session.cpp | 14 +++++++--
 src/audio_core/device/device_session.h   | 12 +++++---
 src/audio_core/in/audio_in_system.cpp    |  2 +-
 src/audio_core/in/audio_in_system.h      | 13 +++++----
 src/audio_core/out/audio_out_system.cpp  |  4 +--
 src/audio_core/out/audio_out_system.h    | 13 +++++----
 src/core/hle/service/audio/audin_u.cpp   | 36 +++++++++++++++++++-----
 src/core/hle/service/audio/audout_u.cpp  | 26 +++++++++++++----
 8 files changed, 85 insertions(+), 35 deletions(-)

diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index ee42ae529..3c214ec00 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -10,6 +10,8 @@
 #include "core/core_timing.h"
 #include "core/memory.h"
 
+#include "core/hle/kernel/k_process.h"
+
 namespace AudioCore {
 
 using namespace std::literals;
@@ -25,7 +27,7 @@ DeviceSession::~DeviceSession() {
 }
 
 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_) {
     if (stream) {
         Finalize();
@@ -36,6 +38,7 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for
     channel_count = channel_count_;
     session_id = session_id_;
     handle = handle_;
+    handle->Open();
     applet_resource_user_id = applet_resource_user_id_;
 
     if (type == Sink::StreamType::In) {
@@ -54,6 +57,11 @@ void DeviceSession::Finalize() {
         sink->CloseStream(stream);
         stream = nullptr;
     }
+
+    if (handle) {
+        handle->Close();
+        handle = nullptr;
+    }
 }
 
 void DeviceSession::Start() {
@@ -91,7 +99,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
             stream->AppendBuffer(new_buffer, tmp_samples);
         } else {
             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);
         }
     }
@@ -100,7 +108,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
 void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
     if (type == Sink::StreamType::In) {
         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);
     }
 }
 
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 7d52f362d..f3fae2617 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -20,6 +20,10 @@ struct EventType;
 } // namespace Timing
 } // namespace Core
 
+namespace Kernel {
+class KProcess;
+} // namespace Kernel
+
 namespace AudioCore {
 
 namespace Sink {
@@ -44,13 +48,13 @@ public:
      * @param sample_format           - Sample format for this device's output.
      * @param channel_count           - Number of channels for this device (2 or 6).
      * @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 type                    - Type of this stream (Render, In, Out).
      * @return Result code for this call.
      */
     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);
 
     /**
@@ -137,8 +141,8 @@ private:
     u16 channel_count{};
     /// Session id of this device session
     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
     u64 applet_resource_user_id{};
     /// Total number of samples played by this device session
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index 579129121..b2dd3ef9f 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/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,
-                          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)};
     if (result.IsError()) {
         return result;
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
index 1c5154638..ee048190c 100644
--- a/src/audio_core/in/audio_in_system.h
+++ b/src/audio_core/in/audio_in_system.h
@@ -19,7 +19,8 @@ class System;
 
 namespace Kernel {
 class KEvent;
-}
+class KProcess;
+} // namespace Kernel
 
 namespace AudioCore::AudioIn {
 
@@ -93,12 +94,12 @@ public:
      *
      * @param device_name             - The name of the requested input device.
      * @param in_params               - Input parameters, see AudioInParameter.
-     * @param handle                  - Unused.
+     * @param handle                  - Process handle.
      * @param applet_resource_user_id - Unused.
      * @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.
@@ -244,8 +245,8 @@ public:
 private:
     /// Core system
     Core::System& system;
-    /// (Unused)
-    u32 handle{};
+    /// Process handle
+    Kernel::KProcess* handle{};
     /// (Unused)
     u64 applet_resource_user_id{};
     /// Buffer event, signalled when a buffer is ready
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 0adf64bd3..7b3ff4e88 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -48,8 +48,8 @@ Result System::IsConfigValid(std::string_view device_name,
     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);
     if (result.IsError()) {
         return result;
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
index b95cb91be..82aada185 100644
--- a/src/audio_core/out/audio_out_system.h
+++ b/src/audio_core/out/audio_out_system.h
@@ -19,7 +19,8 @@ class System;
 
 namespace Kernel {
 class KEvent;
-}
+class KProcess;
+} // namespace Kernel
 
 namespace AudioCore::AudioOut {
 
@@ -84,12 +85,12 @@ public:
      *
      * @param device_name             - The name of the requested output device.
      * @param in_params               - Input parameters, see AudioOutParameter.
-     * @param handle                  - Unused.
+     * @param handle                  - Process handle.
      * @param applet_resource_user_id - Unused.
      * @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.
@@ -228,8 +229,8 @@ public:
 private:
     /// Core system
     Core::System& system;
-    /// (Unused)
-    u32 handle{};
+    /// Process handle
+    Kernel::KProcess* handle{};
     /// (Unused)
     u64 applet_resource_user_id{};
     /// Buffer event, signalled when a buffer is ready
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 56fee4591..de2aa6906 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -18,11 +18,11 @@ using namespace AudioCore::AudioIn;
 class IAudioIn final : public ServiceFramework<IAudioIn> {
 public:
     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"},
           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
         static const FunctionInfo functions[] = {
             {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
@@ -45,6 +45,8 @@ public:
 
         RegisterHandlers(functions);
 
+        process->Open();
+
         if (impl->GetSystem()
                 .Initialize(device_name, in_params, handle, applet_resource_user_id)
                 .IsError()) {
@@ -55,6 +57,7 @@ public:
     ~IAudioIn() override {
         impl->Free();
         service_context.CloseEvent(event);
+        process->Close();
     }
 
     [[nodiscard]] std::shared_ptr<In> GetImpl() {
@@ -196,6 +199,7 @@ private:
 
     KernelHelpers::ServiceContext service_context;
     Kernel::KEvent* event;
+    Kernel::KProcess* process;
     std::shared_ptr<AudioCore::AudioIn::In> impl;
     Common::ScratchBuffer<u64> released_buffer;
 };
@@ -267,6 +271,14 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
     auto device_name = Common::StringFromBuffer(device_name_data);
     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};
     auto link{impl->LinkToManager()};
     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,
               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->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 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};
     auto link{impl->LinkToManager()};
     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,
               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->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
 
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index ca683d72c..8cc7b69f4 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -26,9 +26,10 @@ class IAudioOut final : public ServiceFramework<IAudioOut> {
 public:
     explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
                        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"},
-          event{service_context.CreateEvent("AudioOutEvent")},
+          event{service_context.CreateEvent("AudioOutEvent")}, process{handle},
           impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
 
         // clang-format off
@@ -50,11 +51,14 @@ public:
         };
         // clang-format on
         RegisterHandlers(functions);
+
+        process->Open();
     }
 
     ~IAudioOut() override {
         impl->Free();
         service_context.CloseEvent(event);
+        process->Close();
     }
 
     [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
@@ -206,6 +210,7 @@ private:
 
     KernelHelpers::ServiceContext service_context;
     Kernel::KEvent* event;
+    Kernel::KProcess* process;
     std::shared_ptr<AudioCore::AudioOut::Out> impl;
     Common::ScratchBuffer<u64> released_buffer;
 };
@@ -257,6 +262,14 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
     auto device_name = Common::StringFromBuffer(device_name_data);
     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()};
     if (link.IsError()) {
         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,
               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()) {
         LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
         IPC::ResponseBuilder rb{ctx, 2};

From f2fed21c1139c8d5c030bc5caee5c612dfe7979f Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sun, 7 Jan 2024 13:59:48 -0500
Subject: [PATCH 23/33] kernel: fix page leak on process termination

---
 src/common/page_table.cpp                 | 34 +++-------
 src/core/hle/kernel/k_page_table_base.cpp | 75 ++++++++++++++++++++++-
 src/core/hle/kernel/k_page_table_base.h   |  1 +
 src/core/hle/kernel/k_process.cpp         |  6 ++
 4 files changed, 91 insertions(+), 25 deletions(-)

diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index 166dc3dce..85dc18c11 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "common/page_table.h"
+#include "common/scope_exit.h"
 
 namespace Common {
 
@@ -11,29 +12,10 @@ PageTable::~PageTable() noexcept = default;
 
 bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
                                Common::ProcessAddress address) const {
-    // Setup invalid defaults.
-    out_entry->phys_addr = 0;
-    out_entry->block_size = page_size;
-    out_context->next_page = 0;
+    out_context->next_offset = GetInteger(address);
+    out_context->next_page = address / page_size;
 
-    // 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;
-    }
-
-    // Populate the results.
-    out_entry->phys_addr = phys_addr + GetInteger(address);
-    out_context->next_page = page + 1;
-    out_context->next_offset = GetInteger(address) + page_size;
-
-    return true;
+    return this->ContinueTraversal(out_entry, out_context);
 }
 
 bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
@@ -41,6 +23,12 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
     out_entry->phys_addr = 0;
     out_entry->block_size = page_size;
 
+    // Regardless of whether the page was mapped, advance on exit.
+    SCOPE_EXIT({
+        context->next_page += 1;
+        context->next_offset += page_size;
+    });
+
     // Validate that we can read the actual entry.
     const auto page = context->next_page;
     if (page >= backing_addr.size()) {
@@ -55,8 +43,6 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
 
     // Populate the results.
     out_entry->phys_addr = phys_addr + context->next_offset;
-    context->next_page = page + 1;
-    context->next_offset += page_size;
 
     return true;
 }
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
index 73fbda331..f01eaa164 100644
--- a/src/core/hle/kernel/k_page_table_base.cpp
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -431,9 +431,82 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
                                                m_memory_block_slab_manager));
 }
 
+Result KPageTableBase::FinalizeProcess() {
+    // Only process tables should be finalized.
+    ASSERT(!this->IsKernel());
+
+    // HLE processes don't have memory mapped.
+    R_SUCCEED_IF(m_impl == nullptr);
+
+    // NOTE: Here Nintendo calls an unknown OnFinalize function.
+    // this->OnFinalize();
+
+    // NOTE: Here Nintendo calls a second unknown OnFinalize function.
+    // this->OnFinalize2();
+
+    // Get implementation objects.
+    auto& impl = this->GetImpl();
+    auto& mm = m_kernel.MemoryManager();
+
+    // Traverse, freeing all pages.
+    {
+        // Get the address space size.
+        const size_t as_size = this->GetAddressSpaceSize();
+
+        // Begin the traversal.
+        TraversalContext context;
+        TraversalEntry cur_entry = {
+            .phys_addr = 0,
+            .block_size = 0,
+        };
+
+        bool cur_valid = false;
+        TraversalEntry next_entry;
+        bool next_valid;
+        size_t tot_size = 0;
+
+        next_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context),
+                                         this->GetAddressSpaceStart());
+
+        // Iterate over entries.
+        while (true) {
+            if ((!next_valid && !cur_valid) ||
+                (next_valid && cur_valid &&
+                 next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
+                cur_entry.block_size += next_entry.block_size;
+            } else {
+                if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
+                    mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
+                }
+
+                // Update tracking variables.
+                tot_size += cur_entry.block_size;
+                cur_entry = next_entry;
+                cur_valid = next_valid;
+            }
+
+            if (cur_entry.block_size + tot_size >= as_size) {
+                break;
+            }
+
+            next_valid =
+                impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
+        }
+
+        // Handle the last block.
+        if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
+            mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
+        }
+    }
+
+    R_SUCCEED();
+}
+
 void KPageTableBase::Finalize() {
+    this->FinalizeProcess();
+
     auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
-        if (Settings::IsFastmemEnabled()) {
+        if (m_impl->fastmem_arena) {
             m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
         }
     };
diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h
index 077cafc96..748419f86 100644
--- a/src/core/hle/kernel/k_page_table_base.h
+++ b/src/core/hle/kernel/k_page_table_base.h
@@ -241,6 +241,7 @@ public:
                                 KResourceLimit* resource_limit, Core::Memory::Memory& memory,
                                 KProcessAddress aslr_space_start);
 
+    Result FinalizeProcess();
     void Finalize();
 
     bool IsKernel() const {
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 068e71dff..ae332a550 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -171,6 +171,12 @@ void KProcess::Finalize() {
         m_resource_limit->Close();
     }
 
+    // Clear expensive resources, as the destructor is not called for guest objects.
+    for (auto& interface : m_arm_interfaces) {
+        interface.reset();
+    }
+    m_exclusive_monitor.reset();
+
     // Perform inherited finalization.
     KSynchronizationObject::Finalize();
 }

From f90a022d3a20c86399f49a8154847b73bc1b8fd3 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Tue, 2 Jan 2024 17:12:16 -0500
Subject: [PATCH 24/33] kernel: fix debugger and process list lifetime

---
 src/core/debugger/debugger.cpp          | 39 +++++++++---
 src/core/debugger/gdbstub.cpp           | 66 ++++++++++---------
 src/core/debugger/gdbstub.h             | 15 ++++-
 src/core/hle/kernel/kernel.cpp          | 31 ++++++++-
 src/core/hle/kernel/kernel.h            |  6 +-
 src/core/hle/kernel/svc/svc_process.cpp |  8 ++-
 src/core/hle/service/glue/arp.cpp       |  7 +-
 src/core/hle/service/hid/hid.cpp        | 10 ++-
 src/core/hle/service/pm/pm.cpp          | 85 +++++++++++--------------
 9 files changed, 160 insertions(+), 107 deletions(-)

diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 0e270eb50..e86aae846 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -114,7 +114,7 @@ public:
     }
 
     Kernel::KThread* GetActiveThread() override {
-        return state->active_thread;
+        return state->active_thread.GetPointerUnsafe();
     }
 
 private:
@@ -147,11 +147,14 @@ private:
 
         std::scoped_lock lk{connection_lock};
 
+        // Find the process we are going to debug.
+        SetDebugProcess();
+
         // Ensure everything is stopped.
         PauseEmulation();
 
         // Set up the new frontend.
-        frontend = std::make_unique<GDBStub>(*this, system);
+        frontend = std::make_unique<GDBStub>(*this, system, debug_process.GetPointerUnsafe());
 
         // Set the new state. This will tear down any existing state.
         state = ConnectionState{
@@ -194,15 +197,20 @@ private:
             UpdateActiveThread();
 
             if (state->info.type == SignalType::Watchpoint) {
-                frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
+                frontend->Watchpoint(std::addressof(*state->active_thread),
+                                     *state->info.watchpoint);
             } else {
-                frontend->Stopped(state->active_thread);
+                frontend->Stopped(std::addressof(*state->active_thread));
             }
 
             break;
         case SignalType::ShuttingDown:
             frontend->ShuttingDown();
 
+            // Release members.
+            state->active_thread.Reset(nullptr);
+            debug_process.Reset(nullptr);
+
             // Wait for emulation to shut down gracefully now.
             state->signal_pipe.close();
             state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
@@ -222,7 +230,7 @@ private:
                 stopped = true;
                 PauseEmulation();
                 UpdateActiveThread();
-                frontend->Stopped(state->active_thread);
+                frontend->Stopped(state->active_thread.GetPointerUnsafe());
                 break;
             }
             case DebuggerAction::Continue:
@@ -232,7 +240,7 @@ private:
                 MarkResumed([&] {
                     state->active_thread->SetStepState(Kernel::StepState::StepPending);
                     state->active_thread->Resume(Kernel::SuspendType::Debug);
-                    ResumeEmulation(state->active_thread);
+                    ResumeEmulation(state->active_thread.GetPointerUnsafe());
                 });
                 break;
             case DebuggerAction::StepThreadLocked: {
@@ -255,6 +263,7 @@ private:
     }
 
     void PauseEmulation() {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
         Kernel::KScopedSchedulerLock sl{system.Kernel()};
 
         // Put all threads to sleep on next scheduler round.
@@ -264,6 +273,9 @@ private:
     }
 
     void ResumeEmulation(Kernel::KThread* except = nullptr) {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+        Kernel::KScopedSchedulerLock sl{system.Kernel()};
+
         // Wake up all threads.
         for (auto& thread : ThreadList()) {
             if (std::addressof(thread) == except) {
@@ -277,15 +289,16 @@ private:
 
     template <typename Callback>
     void MarkResumed(Callback&& cb) {
-        Kernel::KScopedSchedulerLock sl{system.Kernel()};
         stopped = false;
         cb();
     }
 
     void UpdateActiveThread() {
+        Kernel::KScopedLightLock ll{debug_process->GetListLock()};
+
         auto& threads{ThreadList()};
         for (auto& thread : threads) {
-            if (std::addressof(thread) == state->active_thread) {
+            if (std::addressof(thread) == state->active_thread.GetPointerUnsafe()) {
                 // Thread is still alive, no need to update.
                 return;
             }
@@ -293,12 +306,18 @@ private:
         state->active_thread = std::addressof(threads.front());
     }
 
+private:
+    void SetDebugProcess() {
+        debug_process = std::move(system.Kernel().GetProcessList().back());
+    }
+
     Kernel::KProcess::ThreadList& ThreadList() {
-        return system.ApplicationProcess()->GetThreadList();
+        return debug_process->GetThreadList();
     }
 
 private:
     System& system;
+    Kernel::KScopedAutoObject<Kernel::KProcess> debug_process;
     std::unique_ptr<DebuggerFrontend> frontend;
 
     boost::asio::io_context io_context;
@@ -310,7 +329,7 @@ private:
         boost::process::async_pipe signal_pipe;
 
         SignalInfo info;
-        Kernel::KThread* active_thread;
+        Kernel::KScopedAutoObject<Kernel::KThread> active_thread;
         std::array<u8, 4096> client_data;
         bool pipe_data;
     };
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 4051ed4af..80091cc7e 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -108,9 +108,9 @@ static std::string EscapeXML(std::string_view data) {
     return escaped;
 }
 
-GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
-    : DebuggerFrontend(backend_), system{system_} {
-    if (system.ApplicationProcess()->Is64Bit()) {
+GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_, Kernel::KProcess* debug_process_)
+    : DebuggerFrontend(backend_), system{system_}, debug_process{debug_process_} {
+    if (GetProcess()->Is64Bit()) {
         arch = std::make_unique<GDBStubA64>();
     } else {
         arch = std::make_unique<GDBStubA32>();
@@ -276,7 +276,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
 
         std::vector<u8> mem(size);
-        if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
+        if (GetMemory().ReadBlock(addr, mem.data(), size)) {
             // Restore any bytes belonging to replaced instructions.
             auto it = replaced_instructions.lower_bound(addr);
             for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
@@ -310,8 +310,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         const auto mem_substr{std::string_view(command).substr(mem_sep)};
         const auto mem{Common::HexStringToVector(mem_substr, false)};
 
-        if (system.ApplicationMemory().WriteBlock(addr, mem.data(), size)) {
-            Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, size);
+        if (GetMemory().WriteBlock(addr, mem.data(), size)) {
+            Core::InvalidateInstructionCacheRange(GetProcess(), addr, size);
             SendReply(GDB_STUB_REPLY_OK);
         } else {
             SendReply(GDB_STUB_REPLY_ERR);
@@ -353,7 +353,7 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
     const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
     const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
 
-    if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
+    if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
         SendReply(GDB_STUB_REPLY_ERR);
         return;
     }
@@ -362,22 +362,20 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
 
     switch (type) {
     case BreakpointType::Software:
-        replaced_instructions[addr] = system.ApplicationMemory().Read32(addr);
-        system.ApplicationMemory().Write32(addr, arch->BreakpointInstruction());
-        Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
+        replaced_instructions[addr] = GetMemory().Read32(addr);
+        GetMemory().Write32(addr, arch->BreakpointInstruction());
+        Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
         success = true;
         break;
     case BreakpointType::WriteWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Write);
+        success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
         break;
     case BreakpointType::ReadWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Read);
+        success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
         break;
     case BreakpointType::AccessWatch:
-        success = system.ApplicationProcess()->InsertWatchpoint(
-            addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+        success =
+            GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
         break;
     case BreakpointType::Hardware:
     default:
@@ -400,7 +398,7 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
     const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
     const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
 
-    if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
+    if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
         SendReply(GDB_STUB_REPLY_ERR);
         return;
     }
@@ -411,24 +409,22 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
     case BreakpointType::Software: {
         const auto orig_insn{replaced_instructions.find(addr)};
         if (orig_insn != replaced_instructions.end()) {
-            system.ApplicationMemory().Write32(addr, orig_insn->second);
-            Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
+            GetMemory().Write32(addr, orig_insn->second);
+            Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
             replaced_instructions.erase(addr);
             success = true;
         }
         break;
     }
     case BreakpointType::WriteWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Write);
+        success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
         break;
     case BreakpointType::ReadWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
-                                                                Kernel::DebugWatchpointType::Read);
+        success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
         break;
     case BreakpointType::AccessWatch:
-        success = system.ApplicationProcess()->RemoveWatchpoint(
-            addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
+        success =
+            GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
         break;
     case BreakpointType::Hardware:
     default:
@@ -466,10 +462,10 @@ void GDBStub::HandleQuery(std::string_view command) {
         const auto target_xml{arch->GetTargetXML()};
         SendReply(PaginateBuffer(target_xml, command.substr(30)));
     } else if (command.starts_with("Offsets")) {
-        const auto main_offset = Core::FindMainModuleEntrypoint(system.ApplicationProcess());
+        const auto main_offset = Core::FindMainModuleEntrypoint(GetProcess());
         SendReply(fmt::format("TextSeg={:x}", GetInteger(main_offset)));
     } else if (command.starts_with("Xfer:libraries:read::")) {
-        auto modules = Core::FindModules(system.ApplicationProcess());
+        auto modules = Core::FindModules(GetProcess());
 
         std::string buffer;
         buffer += R"(<?xml version="1.0"?>)";
@@ -483,7 +479,7 @@ void GDBStub::HandleQuery(std::string_view command) {
         SendReply(PaginateBuffer(buffer, command.substr(21)));
     } else if (command.starts_with("fThreadInfo")) {
         // beginning of list
-        const auto& threads = system.ApplicationProcess()->GetThreadList();
+        const auto& threads = GetProcess()->GetThreadList();
         std::vector<std::string> thread_ids;
         for (const auto& thread : threads) {
             thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
@@ -497,7 +493,7 @@ void GDBStub::HandleQuery(std::string_view command) {
         buffer += R"(<?xml version="1.0"?>)";
         buffer += "<threads>";
 
-        const auto& threads = system.ApplicationProcess()->GetThreadList();
+        const auto& threads = GetProcess()->GetThreadList();
         for (const auto& thread : threads) {
             auto thread_name{Core::GetThreadName(&thread)};
             if (!thread_name) {
@@ -613,7 +609,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
     std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
     std::string reply;
 
-    auto* process = system.ApplicationProcess();
+    auto* process = GetProcess();
     auto& page_table = process->GetPageTable();
 
     const char* commands = "Commands:\n"
@@ -714,7 +710,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
 }
 
 Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
-    auto& threads{system.ApplicationProcess()->GetThreadList()};
+    auto& threads{GetProcess()->GetThreadList()};
     for (auto& thread : threads) {
         if (thread.GetThreadId() == thread_id) {
             return std::addressof(thread);
@@ -783,4 +779,12 @@ void GDBStub::SendStatus(char status) {
     backend.WriteToClient(buf);
 }
 
+Kernel::KProcess* GDBStub::GetProcess() {
+    return debug_process;
+}
+
+Core::Memory::Memory& GDBStub::GetMemory() {
+    return GetProcess()->GetMemory();
+}
+
 } // namespace Core
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
index 368197920..232dcf49f 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -12,13 +12,22 @@
 #include "core/debugger/debugger_interface.h"
 #include "core/debugger/gdbstub_arch.h"
 
+namespace Kernel {
+class KProcess;
+}
+
+namespace Core::Memory {
+class Memory;
+}
+
 namespace Core {
 
 class System;
 
 class GDBStub : public DebuggerFrontend {
 public:
-    explicit GDBStub(DebuggerBackend& backend, Core::System& system);
+    explicit GDBStub(DebuggerBackend& backend, Core::System& system,
+                     Kernel::KProcess* debug_process);
     ~GDBStub() override;
 
     void Connected() override;
@@ -42,8 +51,12 @@ private:
     void SendReply(std::string_view data);
     void SendStatus(char status);
 
+    Kernel::KProcess* GetProcess();
+    Core::Memory::Memory& GetMemory();
+
 private:
     Core::System& system;
+    Kernel::KProcess* debug_process;
     std::unique_ptr<GDBStubArch> arch;
     std::vector<char> current_command;
     std::map<VAddr, u32> replaced_instructions;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 1030f0c12..f3683cdcc 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -112,7 +112,14 @@ struct KernelCore::Impl {
             old_process->Close();
         }
 
-        process_list.clear();
+        {
+            std::scoped_lock lk{process_list_lock};
+            for (auto* const process : process_list) {
+                process->Terminate();
+                process->Close();
+            }
+            process_list.clear();
+        }
 
         next_object_id = 0;
         next_kernel_process_id = KProcess::InitialProcessIdMin;
@@ -770,6 +777,7 @@ struct KernelCore::Impl {
     std::atomic<u64> next_thread_id{1};
 
     // Lists all processes that exist in the current session.
+    std::mutex process_list_lock;
     std::vector<KProcess*> process_list;
     std::atomic<KProcess*> application_process{};
     std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
@@ -869,9 +877,19 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() {
 }
 
 void KernelCore::AppendNewProcess(KProcess* process) {
+    process->Open();
+
+    std::scoped_lock lk{impl->process_list_lock};
     impl->process_list.push_back(process);
 }
 
+void KernelCore::RemoveProcess(KProcess* process) {
+    std::scoped_lock lk{impl->process_list_lock};
+    if (std::erase(impl->process_list, process)) {
+        process->Close();
+    }
+}
+
 void KernelCore::MakeApplicationProcess(KProcess* process) {
     impl->MakeApplicationProcess(process);
 }
@@ -884,8 +902,15 @@ const KProcess* KernelCore::ApplicationProcess() const {
     return impl->application_process;
 }
 
-const std::vector<KProcess*>& KernelCore::GetProcessList() const {
-    return impl->process_list;
+std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
+    std::list<KScopedAutoObject<KProcess>> processes;
+    std::scoped_lock lk{impl->process_list_lock};
+
+    for (auto* const process : impl->process_list) {
+        processes.emplace_back(process);
+    }
+
+    return processes;
 }
 
 Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 5d4102145..8ea5bed1c 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -5,6 +5,7 @@
 
 #include <array>
 #include <functional>
+#include <list>
 #include <memory>
 #include <string>
 #include <unordered_map>
@@ -116,8 +117,9 @@ public:
     /// Retrieves a shared pointer to the system resource limit instance.
     KResourceLimit* GetSystemResourceLimit();
 
-    /// Adds the given shared pointer to an internal list of active processes.
+    /// Adds/removes the given pointer to an internal list of active processes.
     void AppendNewProcess(KProcess* process);
+    void RemoveProcess(KProcess* process);
 
     /// Makes the given process the new application process.
     void MakeApplicationProcess(KProcess* process);
@@ -129,7 +131,7 @@ public:
     const KProcess* ApplicationProcess() const;
 
     /// Retrieves the list of processes.
-    const std::vector<KProcess*>& GetProcessList() const;
+    std::list<KScopedAutoObject<KProcess>> GetProcessList();
 
     /// Gets the sole instance of the global scheduler
     Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp
index caa8bee9a..5c3e8829f 100644
--- a/src/core/hle/kernel/svc/svc_process.cpp
+++ b/src/core/hle/kernel/svc/svc_process.cpp
@@ -74,13 +74,15 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc
     }
 
     auto& memory = GetCurrentMemory(kernel);
-    const auto& process_list = kernel.GetProcessList();
+    auto process_list = kernel.GetProcessList();
+    auto it = process_list.begin();
+
     const auto num_processes = process_list.size();
     const auto copy_amount =
         std::min(static_cast<std::size_t>(out_process_ids_size), num_processes);
 
-    for (std::size_t i = 0; i < copy_amount; ++i) {
-        memory.Write64(out_process_ids, process_list[i]->GetProcessId());
+    for (std::size_t i = 0; i < copy_amount && it != process_list.end(); ++i, ++it) {
+        memory.Write64(out_process_ids, (*it)->GetProcessId());
         out_process_ids += sizeof(u64);
     }
 
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index 6f1151b03..1254b6d49 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -15,9 +15,10 @@
 namespace Service::Glue {
 
 namespace {
-std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
-    const auto& list = system.Kernel().GetProcessList();
-    const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
+std::optional<u64> GetTitleIDForProcessID(Core::System& system, u64 process_id) {
+    auto list = system.Kernel().GetProcessList();
+
+    const auto iter = std::find_if(list.begin(), list.end(), [&process_id](auto& process) {
         return process->GetProcessId() == process_id;
     });
 
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index fc03a0a5f..4ce0a9834 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -22,12 +22,10 @@ void LoopProcess(Core::System& system) {
     std::shared_ptr<HidFirmwareSettings> firmware_settings =
         std::make_shared<HidFirmwareSettings>();
 
-    // TODO: Remove this hack until this service is emulated properly.
-    const auto process_list = system.Kernel().GetProcessList();
-    if (!process_list.empty()) {
-        resource_manager->Initialize();
-        resource_manager->RegisterAppletResourceUserId(process_list[0]->GetId(), true);
-    }
+    // TODO: Remove this hack when am is emulated properly.
+    resource_manager->Initialize();
+    resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(),
+                                                   true);
 
     server_manager->RegisterNamedService(
         "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index d92499f05..b52468e41 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -22,27 +22,26 @@ constexpr Result ResultProcessNotFound{ErrorModule::PM, 1};
 
 constexpr u64 NO_PROCESS_FOUND_PID{0};
 
-std::optional<Kernel::KProcess*> SearchProcessList(
-    const std::vector<Kernel::KProcess*>& process_list,
-    std::function<bool(Kernel::KProcess*)> predicate) {
+using ProcessList = std::list<Kernel::KScopedAutoObject<Kernel::KProcess>>;
+
+template <typename F>
+Kernel::KScopedAutoObject<Kernel::KProcess> SearchProcessList(ProcessList& process_list,
+                                                              F&& predicate) {
     const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate);
 
     if (iter == process_list.end()) {
-        return std::nullopt;
+        return nullptr;
     }
 
-    return *iter;
+    return iter->GetPointerUnsafe();
 }
 
-void GetApplicationPidGeneric(HLERequestContext& ctx,
-                              const std::vector<Kernel::KProcess*>& process_list) {
-    const auto process = SearchProcessList(process_list, [](const auto& proc) {
-        return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin;
-    });
+void GetApplicationPidGeneric(HLERequestContext& ctx, ProcessList& process_list) {
+    auto process = SearchProcessList(process_list, [](auto& p) { return p->IsApplication(); });
 
     IPC::ResponseBuilder rb{ctx, 4};
     rb.Push(ResultSuccess);
-    rb.Push(process.has_value() ? (*process)->GetProcessId() : NO_PROCESS_FOUND_PID);
+    rb.Push(process.IsNull() ? NO_PROCESS_FOUND_PID : process->GetProcessId());
 }
 
 } // Anonymous namespace
@@ -80,8 +79,7 @@ private:
 
 class DebugMonitor final : public ServiceFramework<DebugMonitor> {
 public:
-    explicit DebugMonitor(Core::System& system_)
-        : ServiceFramework{system_, "pm:dmnt"}, kernel{system_.Kernel()} {
+    explicit DebugMonitor(Core::System& system_) : ServiceFramework{system_, "pm:dmnt"} {
         // clang-format off
         static const FunctionInfo functions[] = {
             {0, nullptr, "GetJitDebugProcessIdList"},
@@ -106,12 +104,11 @@ private:
 
         LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
 
-        const auto process =
-            SearchProcessList(kernel.GetProcessList(), [program_id](const auto& proc) {
-                return proc->GetProgramId() == program_id;
-            });
+        auto list = kernel.GetProcessList();
+        auto process = SearchProcessList(
+            list, [program_id](auto& p) { return p->GetProgramId() == program_id; });
 
-        if (!process.has_value()) {
+        if (process.IsNull()) {
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             return;
@@ -119,12 +116,13 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProcessId());
+        rb.Push(process->GetProcessId());
     }
 
     void GetApplicationProcessId(HLERequestContext& ctx) {
         LOG_DEBUG(Service_PM, "called");
-        GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+        auto list = kernel.GetProcessList();
+        GetApplicationPidGeneric(ctx, list);
     }
 
     void AtmosphereGetProcessInfo(HLERequestContext& ctx) {
@@ -135,11 +133,10 @@ private:
 
         LOG_WARNING(Service_PM, "(Partial Implementation) called, pid={:016X}", pid);
 
-        const auto process = SearchProcessList(kernel.GetProcessList(), [pid](const auto& proc) {
-            return proc->GetProcessId() == pid;
-        });
+        auto list = kernel.GetProcessList();
+        auto process = SearchProcessList(list, [pid](auto& p) { return p->GetProcessId() == pid; });
 
-        if (!process.has_value()) {
+        if (process.IsNull()) {
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             return;
@@ -159,7 +156,7 @@ private:
 
         OverrideStatus override_status{};
         ProgramLocation program_location{
-            .program_id = (*process)->GetProgramId(),
+            .program_id = process->GetProgramId(),
             .storage_id = 0,
         };
 
@@ -169,14 +166,11 @@ private:
         rb.PushRaw(program_location);
         rb.PushRaw(override_status);
     }
-
-    const Kernel::KernelCore& kernel;
 };
 
 class Info final : public ServiceFramework<Info> {
 public:
-    explicit Info(Core::System& system_, const std::vector<Kernel::KProcess*>& process_list_)
-        : ServiceFramework{system_, "pm:info"}, process_list{process_list_} {
+    explicit Info(Core::System& system_) : ServiceFramework{system_, "pm:info"} {
         static const FunctionInfo functions[] = {
             {0, &Info::GetProgramId, "GetProgramId"},
             {65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
@@ -193,11 +187,11 @@ private:
 
         LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id);
 
-        const auto process = SearchProcessList(process_list, [process_id](const auto& proc) {
-            return proc->GetProcessId() == process_id;
-        });
+        auto list = kernel.GetProcessList();
+        auto process = SearchProcessList(
+            list, [process_id](auto& p) { return p->GetProcessId() == process_id; });
 
-        if (!process.has_value()) {
+        if (process.IsNull()) {
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             return;
@@ -205,7 +199,7 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProgramId());
+        rb.Push(process->GetProgramId());
     }
 
     void AtmosphereGetProcessId(HLERequestContext& ctx) {
@@ -214,11 +208,11 @@ private:
 
         LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
 
-        const auto process = SearchProcessList(process_list, [program_id](const auto& proc) {
-            return proc->GetProgramId() == program_id;
-        });
+        auto list = system.Kernel().GetProcessList();
+        auto process = SearchProcessList(
+            list, [program_id](auto& p) { return p->GetProgramId() == program_id; });
 
-        if (!process.has_value()) {
+        if (process.IsNull()) {
             IPC::ResponseBuilder rb{ctx, 2};
             rb.Push(ResultProcessNotFound);
             return;
@@ -226,16 +220,13 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 4};
         rb.Push(ResultSuccess);
-        rb.Push((*process)->GetProcessId());
+        rb.Push(process->GetProcessId());
     }
-
-    const std::vector<Kernel::KProcess*>& process_list;
 };
 
 class Shell final : public ServiceFramework<Shell> {
 public:
-    explicit Shell(Core::System& system_)
-        : ServiceFramework{system_, "pm:shell"}, kernel{system_.Kernel()} {
+    explicit Shell(Core::System& system_) : ServiceFramework{system_, "pm:shell"} {
         // clang-format off
         static const FunctionInfo functions[] = {
             {0, nullptr, "LaunchProgram"},
@@ -257,10 +248,9 @@ public:
 private:
     void GetApplicationProcessIdForShell(HLERequestContext& ctx) {
         LOG_DEBUG(Service_PM, "called");
-        GetApplicationPidGeneric(ctx, kernel.GetProcessList());
+        auto list = kernel.GetProcessList();
+        GetApplicationPidGeneric(ctx, list);
     }
-
-    const Kernel::KernelCore& kernel;
 };
 
 void LoopProcess(Core::System& system) {
@@ -268,8 +258,7 @@ void LoopProcess(Core::System& system) {
 
     server_manager->RegisterNamedService("pm:bm", std::make_shared<BootMode>(system));
     server_manager->RegisterNamedService("pm:dmnt", std::make_shared<DebugMonitor>(system));
-    server_manager->RegisterNamedService(
-        "pm:info", std::make_shared<Info>(system, system.Kernel().GetProcessList()));
+    server_manager->RegisterNamedService("pm:info", std::make_shared<Info>(system));
     server_manager->RegisterNamedService("pm:shell", std::make_shared<Shell>(system));
     ServerManager::RunServer(std::move(server_manager));
 }

From 2f0b57ca13fb91730d7e210f6f4504357ef6cd0a Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Fri, 12 Jan 2024 19:19:07 -0500
Subject: [PATCH 25/33] kernel: optimize page free on shutdown

---
 .../hle/kernel/k_memory_block_manager.cpp     |  4 +-
 src/core/hle/kernel/k_memory_block_manager.h  |  4 +-
 src/core/hle/kernel/k_page_table_base.cpp     | 73 ++++---------------
 3 files changed, 18 insertions(+), 63 deletions(-)

diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp
index 58a1e7216..f08a6e448 100644
--- a/src/core/hle/kernel/k_memory_block_manager.cpp
+++ b/src/core/hle/kernel/k_memory_block_manager.cpp
@@ -28,14 +28,14 @@ Result KMemoryBlockManager::Initialize(KProcessAddress st, KProcessAddress nd,
 }
 
 void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager,
-                                   HostUnmapCallback&& host_unmap_callback) {
+                                   BlockCallback&& block_callback) {
     // Erase every block until we have none left.
     auto it = m_memory_block_tree.begin();
     while (it != m_memory_block_tree.end()) {
         KMemoryBlock* block = std::addressof(*it);
         it = m_memory_block_tree.erase(it);
+        block_callback(block->GetAddress(), block->GetSize());
         slab_manager->Free(block);
-        host_unmap_callback(block->GetAddress(), block->GetSize());
     }
 
     ASSERT(m_memory_block_tree.empty());
diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h
index cb7b6f430..377628504 100644
--- a/src/core/hle/kernel/k_memory_block_manager.h
+++ b/src/core/hle/kernel/k_memory_block_manager.h
@@ -85,11 +85,11 @@ public:
 public:
     KMemoryBlockManager();
 
-    using HostUnmapCallback = std::function<void(Common::ProcessAddress, u64)>;
+    using BlockCallback = std::function<void(Common::ProcessAddress, u64)>;
 
     Result Initialize(KProcessAddress st, KProcessAddress nd,
                       KMemoryBlockSlabManager* slab_manager);
-    void Finalize(KMemoryBlockSlabManager* slab_manager, HostUnmapCallback&& host_unmap_callback);
+    void Finalize(KMemoryBlockSlabManager* slab_manager, BlockCallback&& block_callback);
 
     iterator end() {
         return m_memory_block_tree.end();
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
index f01eaa164..3f0a39d33 100644
--- a/src/core/hle/kernel/k_page_table_base.cpp
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -435,69 +435,14 @@ Result KPageTableBase::FinalizeProcess() {
     // Only process tables should be finalized.
     ASSERT(!this->IsKernel());
 
-    // HLE processes don't have memory mapped.
-    R_SUCCEED_IF(m_impl == nullptr);
-
     // NOTE: Here Nintendo calls an unknown OnFinalize function.
     // this->OnFinalize();
 
     // NOTE: Here Nintendo calls a second unknown OnFinalize function.
     // this->OnFinalize2();
 
-    // Get implementation objects.
-    auto& impl = this->GetImpl();
-    auto& mm = m_kernel.MemoryManager();
-
-    // Traverse, freeing all pages.
-    {
-        // Get the address space size.
-        const size_t as_size = this->GetAddressSpaceSize();
-
-        // Begin the traversal.
-        TraversalContext context;
-        TraversalEntry cur_entry = {
-            .phys_addr = 0,
-            .block_size = 0,
-        };
-
-        bool cur_valid = false;
-        TraversalEntry next_entry;
-        bool next_valid;
-        size_t tot_size = 0;
-
-        next_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context),
-                                         this->GetAddressSpaceStart());
-
-        // Iterate over entries.
-        while (true) {
-            if ((!next_valid && !cur_valid) ||
-                (next_valid && cur_valid &&
-                 next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
-                cur_entry.block_size += next_entry.block_size;
-            } else {
-                if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
-                    mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
-                }
-
-                // Update tracking variables.
-                tot_size += cur_entry.block_size;
-                cur_entry = next_entry;
-                cur_valid = next_valid;
-            }
-
-            if (cur_entry.block_size + tot_size >= as_size) {
-                break;
-            }
-
-            next_valid =
-                impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
-        }
-
-        // Handle the last block.
-        if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
-            mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
-        }
-    }
+    // 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();
 }
@@ -505,14 +450,24 @@ Result KPageTableBase::FinalizeProcess() {
 void KPageTableBase::Finalize() {
     this->FinalizeProcess();
 
-    auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
+    auto BlockCallback = [&](KProcessAddress addr, u64 size) {
         if (m_impl->fastmem_arena) {
             m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
         }
+
+        // Get physical pages.
+        KPageGroup pg(m_kernel, m_block_info_manager);
+        this->MakePageGroup(pg, addr, size / PageSize);
+
+        // Free the pages.
+        pg.CloseAndReset();
     };
 
     // Finalize memory blocks.
-    m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
+    {
+        KScopedLightLock lk(m_general_lock);
+        m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(BlockCallback));
+    }
 
     // Free any unsafe mapped memory.
     if (m_mapped_unsafe_physical_memory) {

From 76880b84f9923a3fbdb53edb049d635d33de5e76 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Sat, 13 Jan 2024 13:45:05 -0500
Subject: [PATCH 26/33] loader: fix homebrew nro registration

---
 src/core/loader/nro.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 83371fcbd..f8225d697 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -275,12 +275,12 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
         return {ResultStatus::ErrorLoadingNRO, {}};
     }
 
-    if (romfs != nullptr) {
-        system.GetFileSystemController().RegisterProcess(
-            process.GetProcessId(), {},
-            std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
-                                                    system.GetFileSystemController()));
-    }
+    u64 program_id{};
+    ReadProgramId(program_id);
+    system.GetFileSystemController().RegisterProcess(
+        process.GetProcessId(), program_id,
+        std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
+                                                system.GetFileSystemController()));
 
     is_loaded = true;
     return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,

From bee22540a1d8a7b3ebd9ff4c244bf257b5e9f8b7 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 13 Jan 2024 14:19:44 -0600
Subject: [PATCH 27/33] service: acc: Only save profiles when profiles have
 changed

---
 src/core/hle/service/acc/profile_manager.cpp  | 19 ++++++++++++++++---
 src/core/hle/service/acc/profile_manager.h    |  1 +
 .../configure_profile_manager.cpp             |  4 ++++
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 5542d6cbc..683f44e27 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -61,9 +61,7 @@ ProfileManager::ProfileManager() {
     OpenUser(*GetUser(current));
 }
 
-ProfileManager::~ProfileManager() {
-    WriteUserSaveFile();
-}
+ProfileManager::~ProfileManager() = default;
 
 /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
 /// internal management of the users profiles
@@ -113,6 +111,8 @@ Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username)
         return ERROR_USER_ALREADY_EXISTS;
     }
 
+    is_save_needed = true;
+
     return AddUser({
         .user_uuid = uuid,
         .username = username,
@@ -326,6 +326,9 @@ bool ProfileManager::RemoveUser(UUID uuid) {
     profiles[*index] = ProfileInfo{};
     std::stable_partition(profiles.begin(), profiles.end(),
                           [](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
+
+    is_save_needed = true;
+
     return true;
 }
 
@@ -340,6 +343,8 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
     profile.username = profile_new.username;
     profile.creation_time = profile_new.timestamp;
 
+    is_save_needed = true;
+
     return true;
 }
 
@@ -348,6 +353,7 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
     const auto index = GetUserIndex(uuid);
     if (index.has_value() && SetProfileBase(uuid, profile_new)) {
         profiles[*index].data = data_new;
+        is_save_needed = true;
         return true;
     }
 
@@ -391,6 +397,10 @@ void ProfileManager::ParseUserSaveFile() {
 }
 
 void ProfileManager::WriteUserSaveFile() {
+    if (!is_save_needed) {
+        return;
+    }
+
     ProfileDataRaw raw{};
 
     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)) {
         LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
                                  "made in current session will be saved.");
+        return;
     }
+
+    is_save_needed = false;
 }
 
 }; // namespace Service::Account
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 900e32200..e21863e64 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -103,6 +103,7 @@ private:
     std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
     bool RemoveProfileAtIndex(std::size_t index);
 
+    bool is_save_needed{};
     std::array<ProfileInfo, MAX_USERS> profiles{};
     std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
     std::size_t user_count{};
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index fa5f383d6..12a04b9a0 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -205,6 +205,7 @@ void ConfigureProfileManager::AddUser() {
 
     const auto uuid = Common::UUID::MakeRandom();
     profile_manager.CreateNewUser(uuid, username.toStdString());
+    profile_manager.WriteUserSaveFile();
 
     item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
 }
@@ -228,6 +229,7 @@ void ConfigureProfileManager::RenameUser() {
     std::copy(username_std.begin(), username_std.end(), profile.username.begin());
 
     profile_manager.SetProfileBase(*uuid, profile);
+    profile_manager.WriteUserSaveFile();
 
     item_model->setItem(
         user, 0,
@@ -256,6 +258,8 @@ void ConfigureProfileManager::DeleteUser(const Common::UUID& uuid) {
         return;
     }
 
+    profile_manager.WriteUserSaveFile();
+
     item_model->removeRows(tree_view->currentIndex().row(), 1);
     tree_view->clearSelection();
 

From cdeaca73c460fa4a85d2f7d493828711f90e8747 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Thu, 11 Jan 2024 21:53:11 -0500
Subject: [PATCH 28/33] android: Move ktlintCheck to yuzu-verify

---
 .ci/scripts/format/script.sh     | 3 +++
 .github/workflows/verify.yml     | 8 +++++---
 src/android/app/build.gradle.kts | 9 ++++++++-
 3 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/.ci/scripts/format/script.sh b/.ci/scripts/format/script.sh
index c22398de0..f9c63dbfa 100755
--- a/.ci/scripts/format/script.sh
+++ b/.ci/scripts/format/script.sh
@@ -32,3 +32,6 @@ if [ ! -z "$DIFF" ]; then
     echo "$DIFF"
     exit 1
 fi
+
+cd src/android
+./gradlew ktlintCheck
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index c073f3f3f..62eb69aeb 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -13,13 +13,15 @@ jobs:
   format:
     name: 'verify format'
     runs-on: ubuntu-latest
-    container:
-      image: yuzuemu/build-environments:linux-clang-format
-      options: -u 1001
     steps:
       - uses: actions/checkout@v3
         with:
           submodules: false
+      - name: set up JDK 17
+        uses: actions/setup-java@v3
+        with:
+          java-version: '17'
+          distribution: 'temurin'
       - name: 'Verify Formatting'
         run: bash -ex ./.ci/scripts/format/script.sh
   build:
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 53aafa08c..d62254dd3 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -188,8 +188,15 @@ tasks.create<Delete>("ktlintReset") {
     delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
 }
 
+val showFormatHelp = {
+    logger.lifecycle(
+        "If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
+            "codestyle fixes"
+    )
+}
+tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
+tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
 tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
-tasks.getByPath("preBuild").dependsOn("ktlintCheck")
 
 ktlint {
     version.set("0.47.1")

From 15d8a405296a0c1902cab1320d0894feb3129a2d Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sat, 13 Jan 2024 18:06:33 -0500
Subject: [PATCH 29/33] android: Clean up git commands in build.gradle

---
 src/android/app/build.gradle.kts | 78 ++++++++------------------------
 1 file changed, 20 insertions(+), 58 deletions(-)

diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 53aafa08c..7318338fe 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -228,71 +228,33 @@ dependencies {
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
 }
 
-fun getGitVersion(): String {
-    var versionName = "0.0"
-
-    try {
-        versionName = ProcessBuilder("git", "describe", "--always", "--long")
+fun runGitCommand(command: List<String>): String {
+    return try {
+        ProcessBuilder(command)
             .directory(project.rootDir)
             .redirectOutput(ProcessBuilder.Redirect.PIPE)
             .redirectError(ProcessBuilder.Redirect.PIPE)
             .start().inputStream.bufferedReader().use { it.readText() }
             .trim()
+    } catch (e: Exception) {
+        logger.error("Cannot find git")
+        ""
+    }
+}
+
+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)?-[^-]+$"), "")
-    } catch (e: Exception) {
-        logger.error("Cannot find git, defaulting to dummy version number")
     }
-
-    if (System.getenv("GITHUB_ACTIONS") != null) {
-        val gitTag = System.getenv("GIT_TAG_NAME")
-        versionName = gitTag ?: versionName
-    }
-
-    return versionName
+    return versionName.ifEmpty { "0.0" }
 }
 
-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()
+fun getGitHash(): String =
+    runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
 
-        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 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 getBranch(): String =
+    runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }

From 7b3941e5d47915590f477b7900fcbd1759168295 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sat, 13 Jan 2024 18:12:19 -0500
Subject: [PATCH 30/33] android: Show version name instead of git hash in the
 about fragment

---
 .../main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt    | 4 ++--
 src/android/app/src/main/res/layout-w600dp/fragment_about.xml | 4 ++--
 src/android/app/src/main/res/layout/fragment_about.xml        | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index a1620fbb7..5b5f800c1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -76,8 +76,8 @@ class AboutFragment : Fragment() {
             binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
         }
 
-        binding.textBuildHash.text = BuildConfig.GIT_HASH
-        binding.buttonBuildHash.setOnClickListener {
+        binding.textVersionName.text = BuildConfig.VERSION_NAME
+        binding.textVersionName.setOnClickListener {
             val clipBoard =
                 requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
             val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
index a26ffbc73..655e49219 100644
--- a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
@@ -147,7 +147,7 @@
                     android:layout_marginHorizontal="20dp" />
 
                 <LinearLayout
-                    android:id="@+id/button_build_hash"
+                    android:id="@+id/button_version_name"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:background="?attr/selectableItemBackground"
@@ -164,7 +164,7 @@
                         android:textAlignment="viewStart" />
 
                     <com.google.android.material.textview.MaterialTextView
-                        android:id="@+id/text_build_hash"
+                        android:id="@+id/text_version_name"
                         style="@style/TextAppearance.Material3.BodyMedium"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index a24f5230e..38090fa50 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -148,7 +148,7 @@
                 android:layout_marginHorizontal="20dp" />
 
             <LinearLayout
-                android:id="@+id/button_build_hash"
+                android:id="@+id/button_version_name"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:paddingVertical="16dp"
@@ -165,7 +165,7 @@
                     android:text="@string/build" />
 
                 <com.google.android.material.textview.MaterialTextView
-                    android:id="@+id/text_build_hash"
+                    android:id="@+id/text_version_name"
                     style="@style/TextAppearance.Material3.BodyMedium"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"

From d4acdac168e4b445fc8cb2e0d5e15616c5dc7dbf Mon Sep 17 00:00:00 2001
From: GPUCode <geoster3d@gmail.com>
Date: Wed, 3 Jan 2024 23:37:41 +0200
Subject: [PATCH 31/33] core: Support multiple modules per patcher

---
 src/core/arm/nce/patcher.cpp                  | 83 ++++++++++++-------
 src/core/arm/nce/patcher.h                    | 27 +++---
 src/core/hle/kernel/k_process.cpp             |  4 +-
 .../loader/deconstructed_rom_directory.cpp    | 75 +++++++++++++----
 src/core/loader/nso.cpp                       | 41 ++++-----
 src/core/loader/nso.h                         |  3 +-
 6 files changed, 154 insertions(+), 79 deletions(-)

diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp
index 47a7a8880..c7285e3a0 100644
--- a/src/core/arm/nce/patcher.cpp
+++ b/src/core/arm/nce/patcher.cpp
@@ -22,14 +22,10 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
 constexpr size_t MaxRelativeBranch = 128_MiB;
 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.
     c.l(m_save_context);
@@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
     // Write load context helper function.
     c.l(m_load_context);
     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.
     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()) {
-            m_exclusives.push_back(i);
+            curr_patch->m_exclusives.push_back(i);
         }
     }
 
     // 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;
+    return true;
 }
 
-void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
+bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
                               const Kernel::CodeSet::Segment& code,
                               Kernel::PhysicalMemory& program_image,
                               EntryTrampolines* out_trampolines) {
@@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
         if (mode == PatchMode::PreText) {
             rc.B(rel.patch_offset - patch_size - rel.module_offset);
         } 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) {
             rc.B(patch_size - rel.patch_offset + rel.module_offset);
         } 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) {
             return GetInteger(load_base) + patch_offset;
         } 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!
-    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);
     }
-    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),
                                       rel);
     }
 
     // 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)};
         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)});
     }
 
     // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
     // 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]};
         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 {
@@ -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
     // 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
     // unwind the stack properly when jumping back.
diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h
index c6d1608c1..a44f385e2 100644
--- a/src/core/arm/nce/patcher.h
+++ b/src/core/arm/nce/patcher.h
@@ -31,9 +31,9 @@ public:
     explicit Patcher();
     ~Patcher();
 
-    void PatchText(const Kernel::PhysicalMemory& program_image,
+    bool PatchText(const Kernel::PhysicalMemory& program_image,
                    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);
     size_t GetSectionSize() const noexcept;
 
@@ -61,16 +61,16 @@ private:
 
 private:
     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) {
-        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);
     }
 
     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);
     }
 
@@ -84,15 +84,22 @@ private:
         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;
-    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_load_context{};
     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
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 068e71dff..8839ddbc2 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -1233,10 +1233,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
     ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
 
 #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;
         const auto& code = code_set.CodeSegment();
-        const auto& patch = code_set.PatchSegment();
         buffer.Protect(GetInteger(base_addr + code.addr), code.size,
                        Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
         buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index c9f8707b7..b2173f697 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -19,8 +19,54 @@
 #include "core/arm/nce/patcher.h"
 #endif
 
+#ifndef HAS_NCE
+namespace Core::NCE {
+class Patcher {};
+} // namespace Core::NCE
+#endif
+
 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_,
                                                                          bool override_update_)
     : 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{};
 
     // 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
     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 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) {
             return {ResultStatus::ErrorLoadingNSO, {}};
         }
 
+        patch_ctx.SaveIndex(i);
         code_size = *tentative_next_load_addr;
     }
 
@@ -184,6 +220,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         return 0;
     }();
 
+    // Add patch size to the total module size
+    code_size += patch_ctx.GetTotalPatchSize();
+
     // Setup the process code layout
     if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
         return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
@@ -204,9 +243,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
 
         const VAddr load_addr{next_load_addr};
         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) {
             return {ResultStatus::ErrorLoadingNSO, {}};
         }
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index b053a0d14..583b7e927 100644
--- a/src/core/loader/nso.cpp
+++ b/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,
                                                bool should_pass_arguments, bool load_into_process,
                                                std::optional<FileSys::PatchManager> pm,
-                                               Core::NCE::Patcher* patch) {
+                                               std::vector<Core::NCE::Patcher>* patches,
+                                               s32 patch_index) {
     if (nso_file.GetSize() < sizeof(NSOHeader)) {
         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.
     const size_t module_start = [&]() -> size_t {
 #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
         return 0;
@@ -160,27 +164,24 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
 #ifdef HAS_NCE
     // If we are computing the process code layout and using nce backend, patch.
     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->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) {
         // 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
 
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 29b86ed4c..6356697e3 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -93,7 +93,8 @@ public:
                                            const FileSys::VfsFile& nso_file, VAddr load_base,
                                            bool should_pass_arguments, bool load_into_process,
                                            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;
 

From 954eb402375313093fbde8f31929ffc66ecccc75 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Mon, 15 Jan 2024 10:30:57 -0500
Subject: [PATCH 32/33] ci: Remove format step from mainline builds

---
 .ci/yuzu-mainline-step2.yml | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/.ci/yuzu-mainline-step2.yml b/.ci/yuzu-mainline-step2.yml
index b294827f4..8bb0572f5 100644
--- a/.ci/yuzu-mainline-step2.yml
+++ b/.ci/yuzu-mainline-step2.yml
@@ -8,17 +8,7 @@ variables:
   DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
 
 stages:
-- stage: format
-  displayName: 'format'
-  jobs:
-  - job: format
-    displayName: 'clang'
-    pool:
-      vmImage: ubuntu-latest
-    steps:
-    - template: ./templates/format-check.yml
 - stage: build
-  dependsOn: format
   displayName: 'build'
   jobs:
   - job: build

From 8876a152279a5e6fc61460bb444174082f705a1c Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Mon, 15 Jan 2024 12:41:49 -0500
Subject: [PATCH 33/33] android: Fix overlay toggle ordering

---
 src/android/app/src/main/res/values/arrays.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 0363ff3b6..78e855bde 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -228,10 +228,10 @@
         <item>R</item>
         <item>ZL</item>
         <item>ZR</item>
-        <item>@string/gamepad_left_stick</item>
-        <item>@string/gamepad_right_stick</item>
         <item>L3</item>
         <item>R3</item>
+        <item>@string/gamepad_left_stick</item>
+        <item>@string/gamepad_right_stick</item>
         <item>@string/gamepad_d_pad</item>
     </string-array>