From ef251c72899167b3447014077b963481fca02b3c Mon Sep 17 00:00:00 2001
From: pineappleEA <pineaea@gmail.com>
Date: Fri, 26 Jan 2024 01:18:56 +0100
Subject: [PATCH] early-access version 4088

---
 README.md                                     |    2 +-
 .../java/org/yuzu/yuzu_emu/NativeLibrary.kt   |   10 +
 .../yuzu_emu/adapters/AbstractDiffAdapter.kt  |   13 +-
 .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt |    2 +-
 .../yuzu_emu/fragments/EmulationFragment.kt   |   42 +-
 .../fragments/MessageDialogFragment.kt        |   34 +-
 .../yuzu/yuzu_emu/fragments/SetupFragment.kt  |    6 +-
 .../yuzu/yuzu_emu/model/DriverViewModel.kt    |    1 +
 .../main/java/org/yuzu/yuzu_emu/model/Game.kt |   16 +-
 .../org/yuzu/yuzu_emu/model/HomeViewModel.kt  |    7 +
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |   42 +
 .../app/src/main/jni/game_metadata.cpp        |   12 +-
 src/android/app/src/main/jni/native.cpp       |   42 +-
 .../app/src/main/res/values/strings.xml       |    3 +
 src/common/overflow.h                         |   18 +
 src/core/CMakeLists.txt                       |  163 ++-
 src/core/core.cpp                             |   12 +-
 src/core/core.h                               |    2 +-
 src/core/crypto/aes_util.h                    |    2 +-
 src/core/crypto/encryption_layer.h            |    2 +-
 src/core/crypto/partition_data_manager.cpp    |    6 +-
 src/core/crypto/partition_data_manager.h      |    2 +-
 src/core/file_sys/bis_factory.cpp             |    5 +-
 src/core/file_sys/bis_factory.h               |    2 +-
 src/core/file_sys/card_image.cpp              |    4 +-
 src/core/file_sys/card_image.h                |    2 +-
 src/core/file_sys/content_archive.cpp         |    2 +-
 src/core/file_sys/content_archive.h           |    2 +-
 src/core/file_sys/control_metadata.cpp        |    2 +-
 src/core/file_sys/control_metadata.h          |    2 +-
 src/core/file_sys/errors.h                    |   26 +-
 src/core/file_sys/fs_directory.h              |   33 +
 src/core/file_sys/fs_file.h                   |   65 +
 src/core/file_sys/fs_filesystem.h             |   39 +
 src/core/file_sys/fs_memory_management.h      |   40 +
 src/core/file_sys/fs_operate_range.h          |   22 +
 src/core/file_sys/fs_path.h                   |  566 ++++++++
 src/core/file_sys/fs_path_utility.h           | 1239 +++++++++++++++++
 src/core/file_sys/fs_string_util.h            |  226 +++
 src/core/file_sys/fsmitm_romfsbuild.cpp       |    4 +-
 src/core/file_sys/fsmitm_romfsbuild.h         |    2 +-
 src/core/file_sys/fssystem/fs_i_storage.h     |    2 +-
 ...ystem_aes_ctr_counter_extended_storage.cpp |    2 +-
 .../fssystem/fssystem_aes_ctr_storage.h       |    2 +-
 .../file_sys/fssystem/fssystem_bucket_tree.h  |    2 +-
 .../fssystem/fssystem_compressed_storage.h    |    2 +-
 ...rchical_integrity_verification_storage.cpp |    2 +-
 ...rarchical_integrity_verification_storage.h |    2 +-
 .../fssystem_hierarchical_sha256_storage.h    |    2 +-
 .../fssystem/fssystem_indirect_storage.h      |    4 +-
 .../fssystem_integrity_romfs_storage.h        |    2 +-
 .../fssystem_nca_file_system_driver.cpp       |    4 +-
 .../fssystem_nca_file_system_driver.h         |    2 +-
 .../file_sys/fssystem/fssystem_nca_reader.cpp |    2 +-
 src/core/file_sys/ips_layer.cpp               |    2 +-
 src/core/file_sys/ips_layer.h                 |    2 +-
 src/core/file_sys/kernel_executable.cpp       |    2 +-
 src/core/file_sys/kernel_executable.h         |    2 +-
 src/core/file_sys/nca_metadata.cpp            |    2 +-
 src/core/file_sys/nca_metadata.h              |    2 +-
 src/core/file_sys/partition_filesystem.cpp    |    2 +-
 src/core/file_sys/partition_filesystem.h      |    2 +-
 src/core/file_sys/patch_manager.cpp           |    6 +-
 src/core/file_sys/patch_manager.h             |    2 +-
 src/core/file_sys/program_metadata.cpp        |    2 +-
 src/core/file_sys/program_metadata.h          |    2 +-
 src/core/file_sys/registered_cache.cpp        |    2 +-
 src/core/file_sys/registered_cache.h          |    2 +-
 src/core/file_sys/romfs.cpp                   |   10 +-
 src/core/file_sys/romfs.h                     |    2 +-
 src/core/file_sys/romfs_factory.h             |    2 +-
 src/core/file_sys/savedata_factory.cpp        |    2 +-
 src/core/file_sys/savedata_factory.h          |    2 +-
 src/core/file_sys/sdmc_factory.cpp            |    2 +-
 src/core/file_sys/sdmc_factory.h              |    2 +-
 src/core/file_sys/submission_package.h        |    2 +-
 .../file_sys/system_archive/mii_model.cpp     |    2 +-
 src/core/file_sys/system_archive/mii_model.h  |    2 +-
 src/core/file_sys/system_archive/ng_word.cpp  |    2 +-
 src/core/file_sys/system_archive/ng_word.h    |    2 +-
 .../file_sys/system_archive/shared_font.cpp   |    2 +-
 .../file_sys/system_archive/shared_font.h     |    2 +-
 .../file_sys/system_archive/system_archive.h  |    2 +-
 .../system_archive/system_version.cpp         |    2 +-
 .../file_sys/system_archive/system_version.h  |    2 +-
 .../system_archive/time_zone_binary.cpp       |    2 +-
 .../system_archive/time_zone_binary.h         |    2 +-
 src/core/file_sys/vfs/vfs.cpp                 |  551 ++++++++
 src/core/file_sys/vfs/vfs.h                   |  326 +++++
 src/core/file_sys/vfs/vfs_cached.cpp          |   63 +
 src/core/file_sys/vfs/vfs_cached.h            |   31 +
 src/core/file_sys/vfs/vfs_concat.cpp          |  192 +++
 src/core/file_sys/vfs/vfs_concat.h            |   57 +
 src/core/file_sys/vfs/vfs_layered.cpp         |  132 ++
 src/core/file_sys/vfs/vfs_layered.h           |   46 +
 src/core/file_sys/vfs/vfs_offset.cpp          |   98 ++
 src/core/file_sys/vfs/vfs_offset.h            |   50 +
 src/core/file_sys/vfs/vfs_real.cpp            |  527 +++++++
 src/core/file_sys/vfs/vfs_real.h              |  148 ++
 src/core/file_sys/vfs/vfs_static.h            |   80 ++
 src/core/file_sys/vfs/vfs_types.h             |   29 +
 src/core/file_sys/vfs/vfs_vector.cpp          |  133 ++
 src/core/file_sys/vfs/vfs_vector.h            |  131 ++
 src/core/file_sys/xts_archive.cpp             |    2 +-
 src/core/file_sys/xts_archive.h               |    2 +-
 src/core/frontend/applets/error.cpp           |    6 +-
 src/core/hle/result.h                         |   38 +-
 .../hle/service/am/applets/applet_error.cpp   |    4 +-
 .../service/am/applets/applet_web_browser.cpp |    8 +-
 .../service/am/applets/applet_web_browser.h   |    2 +-
 src/core/hle/service/audio/audren_u.cpp       |    3 +-
 src/core/hle/service/bcat/backend/backend.h   |    2 +-
 src/core/hle/service/bcat/bcat_module.cpp     |    2 +-
 src/core/hle/service/caps/caps_a.cpp          |   13 +-
 src/core/hle/service/cmif_serialization.h     |  337 +++++
 src/core/hle/service/cmif_types.h             |  234 ++++
 src/core/hle/service/fatal/fatal.cpp          |    4 +-
 .../hle/service/filesystem/filesystem.cpp     |   78 +-
 src/core/hle/service/filesystem/filesystem.h  |   18 +-
 .../service/filesystem/fsp/fs_i_directory.cpp |   84 ++
 .../service/filesystem/fsp/fs_i_directory.h   |   30 +
 .../hle/service/filesystem/fsp/fs_i_file.cpp  |  127 ++
 .../hle/service/filesystem/fsp/fs_i_file.h    |   25 +
 .../filesystem/fsp/fs_i_filesystem.cpp        |  262 ++++
 .../service/filesystem/fsp/fs_i_filesystem.h  |   38 +
 .../service/filesystem/fsp/fs_i_storage.cpp   |   62 +
 .../hle/service/filesystem/fsp/fs_i_storage.h |   23 +
 .../hle/service/filesystem/fsp/fsp_ldr.cpp    |   22 +
 src/core/hle/service/filesystem/fsp/fsp_ldr.h |   20 +
 .../hle/service/filesystem/fsp/fsp_pr.cpp     |   23 +
 src/core/hle/service/filesystem/fsp/fsp_pr.h  |   20 +
 .../hle/service/filesystem/fsp/fsp_srv.cpp    |  727 ++++++++++
 src/core/hle/service/filesystem/fsp/fsp_srv.h |   78 ++
 .../hle/service/filesystem/fsp/fsp_util.h     |   22 +
 .../hle/service/filesystem/romfs_controller.h |    2 +-
 .../filesystem/save_data_controller.cpp       |    6 +-
 .../service/filesystem/save_data_controller.h |    2 +-
 src/core/hle/service/glue/time/manager.cpp    |    2 +-
 src/core/hle/service/glue/time/manager.h      |    2 +-
 .../service/glue/time/time_zone_binary.cpp    |    2 +-
 src/core/hle/service/hle_ipc.cpp              |   16 +
 src/core/hle/service/hle_ipc.h                |    2 +
 src/core/hle/service/jit/jit.cpp              |  275 ++--
 src/core/hle/service/nfc/nfc_interface.cpp    |    2 +-
 src/core/hle/service/ns/ns.cpp                |    2 +-
 src/core/hle/service/ro/ro.cpp                |  192 +--
 src/core/hle/service/service.h                |   16 +
 .../service/set/system_settings_server.cpp    |    4 +-
 src/core/loader/loader.h                      |    2 +-
 src/core/loader/nro.cpp                       |    2 +-
 src/core/reporter.cpp                         |    4 +-
 src/frontend_common/content_manager.h         |   64 +-
 src/yuzu/applets/qt_error.cpp                 |   12 +-
 src/yuzu/configuration/configure_per_game.h   |    2 +-
 .../configuration/configure_per_game_addons.h |    2 +-
 src/yuzu/game_list_worker.cpp                 |    4 +-
 src/yuzu/main.cpp                             |  144 +-
 src/yuzu/main.h                               |    8 +-
 src/yuzu/main.ui                              |    5 -
 src/yuzu_cmd/yuzu.cpp                         |    2 +-
 160 files changed, 7677 insertions(+), 839 deletions(-)
 create mode 100755 src/core/file_sys/fs_directory.h
 create mode 100755 src/core/file_sys/fs_file.h
 create mode 100755 src/core/file_sys/fs_filesystem.h
 create mode 100755 src/core/file_sys/fs_memory_management.h
 create mode 100755 src/core/file_sys/fs_operate_range.h
 create mode 100755 src/core/file_sys/fs_path.h
 create mode 100755 src/core/file_sys/fs_path_utility.h
 create mode 100755 src/core/file_sys/fs_string_util.h
 create mode 100755 src/core/file_sys/vfs/vfs.cpp
 create mode 100755 src/core/file_sys/vfs/vfs.h
 create mode 100755 src/core/file_sys/vfs/vfs_cached.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_cached.h
 create mode 100755 src/core/file_sys/vfs/vfs_concat.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_concat.h
 create mode 100755 src/core/file_sys/vfs/vfs_layered.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_layered.h
 create mode 100755 src/core/file_sys/vfs/vfs_offset.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_offset.h
 create mode 100755 src/core/file_sys/vfs/vfs_real.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_real.h
 create mode 100755 src/core/file_sys/vfs/vfs_static.h
 create mode 100755 src/core/file_sys/vfs/vfs_types.h
 create mode 100755 src/core/file_sys/vfs/vfs_vector.cpp
 create mode 100755 src/core/file_sys/vfs/vfs_vector.h
 create mode 100755 src/core/hle/service/cmif_serialization.h
 create mode 100755 src/core/hle/service/cmif_types.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_directory.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_file.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_file.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_filesystem.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_storage.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fs_i_storage.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_ldr.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_ldr.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_pr.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_pr.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_srv.cpp
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_srv.h
 create mode 100755 src/core/hle/service/filesystem/fsp/fsp_util.h

diff --git a/README.md b/README.md
index c2fabd262..154abe224 100755
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 yuzu emulator early access
 =============
 
-This is the source code for early-access 4087.
+This is the source code for early-access 4088.
 
 ## Legal Notice
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c408485c6..55abba093 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -303,6 +303,11 @@ object NativeLibrary {
      */
     external fun getCpuBackend(): String
 
+    /**
+     * Returns the current GPU Driver.
+     */
+    external fun getGpuDriver(): String
+
     external fun applySettings()
 
     external fun logSettings()
@@ -614,6 +619,11 @@ object NativeLibrary {
      */
     external fun clearFilesystemProvider()
 
+    /**
+     * Checks if all necessary keys are present for decryption
+     */
+    external fun areKeysPresent(): Boolean
+
     /**
      * Button type for use in onTouchEvent
      */
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
index f006f9e3d..0ab1b46c3 100755
--- 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
@@ -14,15 +14,20 @@ 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.
+ * @param exact Decides whether each item will be compared by reference or by their contents
  */
-abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> :
-    ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) {
+abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
+    exact: Boolean = true
+) : ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>(exact)).build()) {
     override fun onBindViewHolder(holder: Holder, position: Int) =
         holder.bind(currentList[position])
 
-    private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() {
+    private class DiffCallback<Model>(val exact: Boolean) : DiffUtil.ItemCallback<Model>() {
         override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
-            return oldItem === newItem
+            if (exact) {
+                return oldItem === newItem
+            }
+            return oldItem == newItem
         }
 
         @SuppressLint("DiffUtilEquals")
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 b4f4d950f..85c8249e6 100755
--- 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
@@ -30,7 +30,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
 import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
 
 class GameAdapter(private val activity: AppCompatActivity) :
-    AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() {
+    AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
         CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
             .also { return GameViewHolder(it) }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 2a97ae14d..d17e087fe 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -38,7 +38,6 @@ import androidx.window.layout.WindowLayoutInfo
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -141,7 +140,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
         // So this fragment doesn't restart on configuration changes; i.e. rotation.
         retainInstance = true
-        emulationState = EmulationState(game.path)
+        emulationState = EmulationState(game.path) {
+            return@EmulationState driverViewModel.isInteractionAllowed.value
+        }
     }
 
     /**
@@ -370,6 +371,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     }
                 }
             }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.RESUMED) {
+                    driverViewModel.isInteractionAllowed.collect {
+                        if (it) {
+                            startEmulation()
+                        }
+                    }
+                }
+            }
             launch {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     emulationViewModel.emulationStarted.collectLatest {
@@ -398,19 +408,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     }
                 }
             }
-            launch {
-                repeatOnLifecycle(Lifecycle.State.RESUMED) {
-                    driverViewModel.isInteractionAllowed.collect {
-                        if (it) {
-                            onEmulationStart()
-                        }
-                    }
-                }
-            }
         }
     }
 
-    private fun onEmulationStart() {
+    private fun startEmulation() {
         if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
             if (!DirectoryInitialization.areDirectoriesReady) {
                 DirectoryInitialization.start()
@@ -485,12 +486,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             val FRAMETIME = 2
             val SPEED = 3
             perfStatsUpdater = {
-                if (emulationViewModel.emulationStarted.value) {
+                if (emulationViewModel.emulationStarted.value &&
+                    !emulationViewModel.isEmulationStopping.value
+                ) {
                     val perfStats = NativeLibrary.getPerfStats()
                     val cpuBackend = NativeLibrary.getCpuBackend()
+                    val gpuDriver = NativeLibrary.getGpuDriver()
                     if (_binding != null) {
                         binding.showFpsText.text =
-                            String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
+                            String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver)
                     }
                     perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
                 }
@@ -807,7 +811,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         }
     }
 
-    private class EmulationState(private val gamePath: String) {
+    private class EmulationState(
+        private val gamePath: String,
+        private val emulationCanStart: () -> Boolean
+    ) {
         private var state: State
         private var surface: Surface? = null
 
@@ -901,6 +908,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     State.PAUSED -> Log.warning(
                         "[EmulationFragment] Surface cleared while emulation paused."
                     )
+
                     else -> Log.warning(
                         "[EmulationFragment] Surface cleared while emulation stopped."
                     )
@@ -910,6 +918,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
         private fun runWithValidSurface() {
             NativeLibrary.surfaceChanged(surface)
+            if (!emulationCanStart.invoke()) {
+                return
+            }
+
             when (state) {
                 State.STOPPED -> {
                     val emulationThread = Thread({
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
index 620d8db7c..22b084b9a 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt
@@ -26,9 +26,15 @@ class MessageDialogFragment : DialogFragment() {
         val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
         val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
         val helpLinkId = requireArguments().getInt(HELP_LINK)
+        val dismissible = requireArguments().getBoolean(DISMISSIBLE)
+        val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION)
 
         val builder = MaterialAlertDialogBuilder(requireContext())
 
+        if (clearPositiveAction) {
+            messageDialogViewModel.positiveAction = null
+        }
+
         if (messageDialogViewModel.positiveAction == null) {
             builder.setPositiveButton(R.string.close, null)
         } else {
@@ -51,6 +57,8 @@ class MessageDialogFragment : DialogFragment() {
             }
         }
 
+        isCancelable = dismissible
+
         return builder.show()
     }
 
@@ -67,6 +75,8 @@ class MessageDialogFragment : DialogFragment() {
         private const val DESCRIPTION_ID = "DescriptionId"
         private const val DESCRIPTION_STRING = "DescriptionString"
         private const val HELP_LINK = "Link"
+        private const val DISMISSIBLE = "Dismissible"
+        private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction"
 
         fun newInstance(
             activity: FragmentActivity? = null,
@@ -75,22 +85,28 @@ class MessageDialogFragment : DialogFragment() {
             descriptionId: Int = 0,
             descriptionString: String = "",
             helpLinkId: Int = 0,
+            dismissible: Boolean = true,
             positiveAction: (() -> Unit)? = null
         ): MessageDialogFragment {
-            val dialog = MessageDialogFragment()
-            val bundle = Bundle()
-            bundle.apply {
-                putInt(TITLE_ID, titleId)
-                putString(TITLE_STRING, titleString)
-                putInt(DESCRIPTION_ID, descriptionId)
-                putString(DESCRIPTION_STRING, descriptionString)
-                putInt(HELP_LINK, helpLinkId)
-            }
+            var clearPositiveAction = false
             if (activity != null) {
                 ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
                     clear()
                     this.positiveAction = positiveAction
                 }
+            } else {
+                clearPositiveAction = true
+            }
+
+            val dialog = MessageDialogFragment()
+            val bundle = Bundle().apply {
+                putInt(TITLE_ID, titleId)
+                putString(TITLE_STRING, titleString)
+                putInt(DESCRIPTION_ID, descriptionId)
+                putString(DESCRIPTION_STRING, descriptionString)
+                putInt(HELP_LINK, helpLinkId)
+                putBoolean(DISMISSIBLE, dismissible)
+                putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction)
             }
             dialog.arguments = bundle
             return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 064342cdd..ebf41a639 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -31,6 +31,7 @@ import androidx.preference.PreferenceManager
 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
 import com.google.android.material.transition.MaterialFadeThrough
 import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.NativeLibrary
 import java.io.File
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -162,7 +163,7 @@ class SetupFragment : Fragment() {
                     R.string.install_prod_keys_warning_help,
                     {
                         val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
-                        if (file.exists()) {
+                        if (file.exists() && NativeLibrary.areKeysPresent()) {
                             StepState.COMPLETE
                         } else {
                             StepState.INCOMPLETE
@@ -347,7 +348,8 @@ class SetupFragment : Fragment() {
     val getProdKey =
         registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
             if (result != null) {
-                if (mainActivity.processKey(result)) {
+                mainActivity.processKey(result)
+                if (NativeLibrary.areKeysPresent()) {
                     keyCallback.onStepCompleted()
                 }
             }
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 15ae3a42b..5ed754c96 100755
--- 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
@@ -144,6 +144,7 @@ class DriverViewModel : ViewModel() {
         val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString())
         val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData
         if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) {
+            setDriverReady()
             return
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index c8a4a2d17..6859b7780 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -70,11 +70,19 @@ class Game(
         }
 
     override fun equals(other: Any?): Boolean {
-        if (other !is Game) {
-            return false
-        }
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
 
-        return hashCode() == other.hashCode()
+        other as Game
+
+        if (title != other.title) return false
+        if (path != other.path) return false
+        if (programId != other.programId) return false
+        if (developer != other.developer) return false
+        if (version != other.version) return false
+        if (isHomebrew != other.isHomebrew) return false
+
+        return true
     }
 
     override fun hashCode(): Int {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 513ac2fc5..cfc777b81 100755
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -31,6 +31,9 @@ class HomeViewModel : ViewModel() {
     private val _reloadPropertiesList = MutableStateFlow(false)
     val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow()
 
+    private val _checkKeys = MutableStateFlow(false)
+    val checkKeys = _checkKeys.asStateFlow()
+
     var navigatedToSetup = false
 
     fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -66,4 +69,8 @@ class HomeViewModel : ViewModel() {
     fun reloadPropertiesList(reload: Boolean) {
         _reloadPropertiesList.value = reload
     }
+
+    fun setCheckKeys(value: Boolean) {
+        _checkKeys.value = value
+    }
 }
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 c2cc29961..b3967d294 100755
--- 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
@@ -64,6 +64,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
 
     override var themeId: Int = 0
 
+    private val CHECKED_DECRYPTION = "CheckedDecryption"
+    private var checkedDecryption = false
+
     override fun onCreate(savedInstanceState: Bundle?) {
         val splashScreen = installSplashScreen()
         splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
@@ -75,6 +78,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
         binding = ActivityMainBinding.inflate(layoutInflater)
         setContentView(binding.root)
 
+        if (savedInstanceState != null) {
+            checkedDecryption = savedInstanceState.getBoolean(CHECKED_DECRYPTION)
+        }
+        if (!checkedDecryption) {
+            val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+                .getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
+            if (!firstTimeSetup) {
+                checkKeys()
+            }
+            checkedDecryption = true
+        }
+
         WindowCompat.setDecorFitsSystemWindows(window, false)
         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
 
@@ -150,6 +165,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                     }
                 }
             }
+            launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    homeViewModel.checkKeys.collect {
+                        if (it) {
+                            checkKeys()
+                            homeViewModel.setCheckKeys(false)
+                        }
+                    }
+                }
+            }
         }
 
         // Dismiss previous notifications (should not happen unless a crash occurred)
@@ -158,6 +183,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
         setInsets()
     }
 
+    private fun checkKeys() {
+        if (!NativeLibrary.areKeysPresent()) {
+            MessageDialogFragment.newInstance(
+                titleId = R.string.keys_missing,
+                descriptionId = R.string.keys_missing_description,
+                helpLinkId = R.string.keys_missing_help
+            ).show(supportFragmentManager, MessageDialogFragment.TAG)
+        }
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putBoolean(CHECKED_DECRYPTION, checkedDecryption)
+    }
+
     fun finishSetup(navController: NavController) {
         navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
         (binding.navigationView as NavigationBarView).setupWithNavController(navController)
@@ -349,6 +389,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                     R.string.install_keys_success,
                     Toast.LENGTH_SHORT
                 ).show()
+                homeViewModel.setCheckKeys(true)
                 gamesViewModel.reloadGames(true)
                 return true
             } else {
@@ -399,6 +440,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
                         firmwarePath.deleteRecursively()
                         cacheFirmwareDir.copyRecursively(firmwarePath, true)
                         NativeLibrary.initializeSystem(true)
+                        homeViewModel.setCheckKeys(true)
                         getString(R.string.save_file_imported_success)
                     }
                 } catch (e: Exception) {
diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp
index 78f604c70..8f0da1413 100755
--- a/src/android/app/src/main/jni/game_metadata.cpp
+++ b/src/android/app/src/main/jni/game_metadata.cpp
@@ -1,12 +1,12 @@
 // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <core/core.h>
-#include <core/file_sys/mode.h>
-#include <core/file_sys/patch_manager.h>
-#include <core/loader/nro.h>
-#include <jni.h>
+#include "core/core.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/patch_manager.h"
 #include "core/loader/loader.h"
+#include "core/loader/nro.h"
+#include "jni.h"
 #include "jni/android_common/android_common.h"
 #include "native.h"
 
@@ -79,7 +79,7 @@ extern "C" {
 jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
                                                                jstring jpath) {
     const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
-        GetJString(env, jpath), FileSys::Mode::Read);
+        GetJString(env, jpath), FileSys::OpenMode::Read);
     if (!file) {
         return false;
     }
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 963f57380..3fd9a500c 100755
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -35,9 +35,10 @@
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/fs_filesystem.h"
 #include "core/file_sys/submission_package.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_real.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_real.h"
 #include "core/frontend/applets/cabinet.h"
 #include "core/frontend/applets/controller.h"
 #include "core/frontend/applets/error.h"
@@ -154,7 +155,7 @@ void EmulationSession::SurfaceChanged() {
 }
 
 void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
-    const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
+    const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read);
     if (!file) {
         return;
     }
@@ -247,6 +248,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
     m_system.GetCpuManager().OnGpuReady();
     m_system.RegisterExitCallback([&] { HaltEmulation(); });
 
+    OnEmulationStarted();
     return Core::SystemResultStatus::Success;
 }
 
@@ -463,8 +465,8 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
     };
 
     return static_cast<int>(
-        ContentManager::InstallNSP(&EmulationSession::GetInstance().System(),
-                                   EmulationSession::GetInstance().System().GetFilesystem().get(),
+        ContentManager::InstallNSP(EmulationSession::GetInstance().System(),
+                                   *EmulationSession::GetInstance().System().GetFilesystem(),
                                    GetJString(env, j_file), callback));
 }
 
@@ -474,8 +476,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
     u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
     std::string updatePath = GetJString(env, jupdatePath);
     std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
-        EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath,
-                                                                           FileSys::Mode::Read));
+        EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
+            updatePath, FileSys::OpenMode::Read));
     for (const auto& item : nsp->GetNCAs()) {
         for (const auto& nca_details : item.second) {
             if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
@@ -674,6 +676,11 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass
     return ToJString(env, "JIT");
 }
 
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject jobj) {
+    return ToJString(env,
+                     EmulationSession::GetInstance().System().GPU().Renderer().GetDeviceVendor());
+}
+
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) {
     EmulationSession::GetInstance().System().ApplySettings();
 }
@@ -713,7 +720,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
                                                                         jobject instance) {
     const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
     auto vfs_nand_dir = EmulationSession::GetInstance().System().GetFilesystem()->OpenDirectory(
-        Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
+        Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read);
 
     const auto user_id = EmulationSession::GetInstance().System().GetProfileManager().GetUser(
         static_cast<std::size_t>(0));
@@ -819,7 +826,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject job
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
                                                      jstring jprogramId) {
     auto program_id = EmulationSession::GetProgramId(env, jprogramId);
-    ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id);
+    ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
 }
 
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
@@ -829,8 +836,9 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj,
                               program_id, GetJString(env, jname));
 }
 
-jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env, jobject jobj,
-                                                                      jobject jcallback) {
+jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* env,
+                                                                           jobject jobj,
+                                                                           jobject jcallback) {
     auto jlambdaClass = env->GetObjectClass(jcallback);
     auto jlambdaInvokeMethod = env->GetMethodID(
         jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
@@ -842,7 +850,7 @@ jobject Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyInstalledContents(JNIEnv* en
 
     auto& session = EmulationSession::GetInstance();
     std::vector<std::string> result = ContentManager::VerifyInstalledContents(
-        &session.System(), session.GetContentProvider(), callback);
+        session.System(), *session.GetContentProvider(), callback);
     jobjectArray jresult =
         env->NewObjectArray(result.size(), IDCache::GetStringClass(), ToJString(env, ""));
     for (size_t i = 0; i < result.size(); ++i) {
@@ -863,7 +871,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje
     };
     auto& session = EmulationSession::GetInstance();
     return static_cast<jint>(
-        ContentManager::VerifyGameContents(&session.System(), GetJString(env, jpath), callback));
+        ContentManager::VerifyGameContents(session.System(), GetJString(env, jpath), callback));
 }
 
 jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
@@ -882,7 +890,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
 
     const auto nandDir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
     auto vfsNandDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(nandDir),
-                                                            FileSys::Mode::Read);
+                                                            FileSys::OpenMode::Read);
 
     const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
         {}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
@@ -912,4 +920,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env,
     EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
 }
 
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) {
+    auto& system = EmulationSession::GetInstance().System();
+    system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
+    return ContentManager::AreKeysPresent();
+}
+
 } // extern "C"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 779eb36a8..3cd1586fd 100755
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -144,6 +144,9 @@
     <string name="no_save_data_found">No save data found</string>
     <string name="verify_installed_content">Verify installed content</string>
     <string name="verify_installed_content_description">Checks all installed content for corruption</string>
+    <string name="keys_missing">Encryption keys are missing</string>
+    <string name="keys_missing_description">Firmware and retail games cannot be decrypted</string>
+    <string name="keys_missing_help">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
 
     <!-- Applet launcher strings -->
     <string name="applets">Applet launcher</string>
diff --git a/src/common/overflow.h b/src/common/overflow.h
index 44d8e7e73..e184ead95 100755
--- a/src/common/overflow.h
+++ b/src/common/overflow.h
@@ -3,6 +3,7 @@
 
 #pragma once
 
+#include <algorithm>
 #include <type_traits>
 #include "bit_cast.h"
 
@@ -19,4 +20,21 @@ inline T WrappingAdd(T lhs, T rhs) {
     return BitCast<T>(lhs_u + rhs_u);
 }
 
+template <typename T>
+    requires(std::is_integral_v<T> && std::is_signed_v<T>)
+inline bool CanAddWithoutOverflow(T lhs, T rhs) {
+#ifdef _MSC_VER
+    if (lhs >= 0 && rhs >= 0) {
+        return WrappingAdd(lhs, rhs) >= std::max(lhs, rhs);
+    } else if (lhs < 0 && rhs < 0) {
+        return WrappingAdd(lhs, rhs) <= std::min(lhs, rhs);
+    } else {
+        return true;
+    }
+#else
+    T res;
+    return !__builtin_add_overflow(lhs, rhs, &res);
+#endif
+}
+
 } // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 2cebedb02..ddd3c9704 100755
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -20,28 +20,49 @@ add_library(core STATIC
     cpu_manager.h
     crypto/aes_util.cpp
     crypto/aes_util.h
+    crypto/ctr_encryption_layer.cpp
+    crypto/ctr_encryption_layer.h
     crypto/encryption_layer.cpp
     crypto/encryption_layer.h
     crypto/key_manager.cpp
     crypto/key_manager.h
     crypto/partition_data_manager.cpp
     crypto/partition_data_manager.h
-    crypto/ctr_encryption_layer.cpp
-    crypto/ctr_encryption_layer.h
     crypto/xts_encryption_layer.cpp
     crypto/xts_encryption_layer.h
-    debugger/debugger_interface.h
     debugger/debugger.cpp
     debugger/debugger.h
-    debugger/gdbstub_arch.cpp
-    debugger/gdbstub_arch.h
+    debugger/debugger_interface.h
     debugger/gdbstub.cpp
     debugger/gdbstub.h
+    debugger/gdbstub_arch.cpp
+    debugger/gdbstub_arch.h
     device_memory_manager.h
     device_memory_manager.inc
     device_memory.cpp
     device_memory.h
+    file_sys/bis_factory.cpp
+    file_sys/bis_factory.h
+    file_sys/card_image.cpp
+    file_sys/card_image.h
+    file_sys/common_funcs.h
+    file_sys/content_archive.cpp
+    file_sys/content_archive.h
+    file_sys/control_metadata.cpp
+    file_sys/control_metadata.h
+    file_sys/errors.h
+    file_sys/fs_directory.h
+    file_sys/fs_file.h
+    file_sys/fs_filesystem.h
+    file_sys/fs_memory_management.h
+    file_sys/fs_operate_range.h
+    file_sys/fs_path.h
+    file_sys/fs_path_utility.h
+    file_sys/fs_string_util.h
+    file_sys/fsmitm_romfsbuild.cpp
+    file_sys/fsmitm_romfsbuild.h
     file_sys/fssystem/fs_i_storage.h
+    file_sys/fssystem/fs_types.h
     file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
     file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h
     file_sys/fssystem/fssystem_aes_ctr_storage.cpp
@@ -83,25 +104,10 @@ add_library(core STATIC
     file_sys/fssystem/fssystem_switch_storage.h
     file_sys/fssystem/fssystem_utility.cpp
     file_sys/fssystem/fssystem_utility.h
-    file_sys/fssystem/fs_types.h
-    file_sys/bis_factory.cpp
-    file_sys/bis_factory.h
-    file_sys/card_image.cpp
-    file_sys/card_image.h
-    file_sys/common_funcs.h
-    file_sys/content_archive.cpp
-    file_sys/content_archive.h
-    file_sys/control_metadata.cpp
-    file_sys/control_metadata.h
-    file_sys/directory.h
-    file_sys/errors.h
-    file_sys/fsmitm_romfsbuild.cpp
-    file_sys/fsmitm_romfsbuild.h
     file_sys/ips_layer.cpp
     file_sys/ips_layer.h
     file_sys/kernel_executable.cpp
     file_sys/kernel_executable.h
-    file_sys/mode.h
     file_sys/nca_metadata.cpp
     file_sys/nca_metadata.h
     file_sys/partition_filesystem.cpp
@@ -146,22 +152,22 @@ add_library(core STATIC
     file_sys/system_archive/system_version.h
     file_sys/system_archive/time_zone_binary.cpp
     file_sys/system_archive/time_zone_binary.h
-    file_sys/vfs.cpp
-    file_sys/vfs.h
-    file_sys/vfs_cached.cpp
-    file_sys/vfs_cached.h
-    file_sys/vfs_concat.cpp
-    file_sys/vfs_concat.h
-    file_sys/vfs_layered.cpp
-    file_sys/vfs_layered.h
-    file_sys/vfs_offset.cpp
-    file_sys/vfs_offset.h
-    file_sys/vfs_real.cpp
-    file_sys/vfs_real.h
-    file_sys/vfs_static.h
-    file_sys/vfs_types.h
-    file_sys/vfs_vector.cpp
-    file_sys/vfs_vector.h
+    file_sys/vfs/vfs.cpp
+    file_sys/vfs/vfs.h
+    file_sys/vfs/vfs_cached.cpp
+    file_sys/vfs/vfs_cached.h
+    file_sys/vfs/vfs_concat.cpp
+    file_sys/vfs/vfs_concat.h
+    file_sys/vfs/vfs_layered.cpp
+    file_sys/vfs/vfs_layered.h
+    file_sys/vfs/vfs_offset.cpp
+    file_sys/vfs/vfs_offset.h
+    file_sys/vfs/vfs_real.cpp
+    file_sys/vfs/vfs_real.h
+    file_sys/vfs/vfs_static.h
+    file_sys/vfs/vfs_types.h
+    file_sys/vfs/vfs_vector.cpp
+    file_sys/vfs/vfs_vector.h
     file_sys/xts_archive.cpp
     file_sys/xts_archive.h
     frontend/applets/cabinet.cpp
@@ -194,7 +200,6 @@ add_library(core STATIC
     hle/kernel/board/nintendo/nx/secure_monitor.h
     hle/kernel/code_set.cpp
     hle/kernel/code_set.h
-    hle/kernel/svc_results.h
     hle/kernel/global_scheduler_context.cpp
     hle/kernel/global_scheduler_context.h
     hle/kernel/init/init_slab_setup.cpp
@@ -204,11 +209,11 @@ add_library(core STATIC
     hle/kernel/k_address_arbiter.h
     hle/kernel/k_address_space_info.cpp
     hle/kernel/k_address_space_info.h
+    hle/kernel/k_affinity_mask.h
     hle/kernel/k_auto_object.cpp
     hle/kernel/k_auto_object.h
     hle/kernel/k_auto_object_container.cpp
     hle/kernel/k_auto_object_container.h
-    hle/kernel/k_affinity_mask.h
     hle/kernel/k_capabilities.cpp
     hle/kernel/k_capabilities.h
     hle/kernel/k_class_token.cpp
@@ -232,9 +237,9 @@ add_library(core STATIC
     hle/kernel/k_event_info.h
     hle/kernel/k_handle_table.cpp
     hle/kernel/k_handle_table.h
-    hle/kernel/k_hardware_timer_base.h
     hle/kernel/k_hardware_timer.cpp
     hle/kernel/k_hardware_timer.h
+    hle/kernel/k_hardware_timer_base.h
     hle/kernel/k_interrupt_manager.cpp
     hle/kernel/k_interrupt_manager.h
     hle/kernel/k_light_client_session.cpp
@@ -261,10 +266,10 @@ add_library(core STATIC
     hle/kernel/k_page_bitmap.h
     hle/kernel/k_page_buffer.cpp
     hle/kernel/k_page_buffer.h
-    hle/kernel/k_page_heap.cpp
-    hle/kernel/k_page_heap.h
     hle/kernel/k_page_group.cpp
     hle/kernel/k_page_group.h
+    hle/kernel/k_page_heap.cpp
+    hle/kernel/k_page_heap.h
     hle/kernel/k_page_table.h
     hle/kernel/k_page_table_base.cpp
     hle/kernel/k_page_table_base.h
@@ -329,8 +334,6 @@ add_library(core STATIC
     hle/kernel/slab_helpers.h
     hle/kernel/svc.cpp
     hle/kernel/svc.h
-    hle/kernel/svc_common.h
-    hle/kernel/svc_types.h
     hle/kernel/svc/svc_activity.cpp
     hle/kernel/svc/svc_address_arbiter.cpp
     hle/kernel/svc/svc_address_translation.cpp
@@ -368,6 +371,9 @@ add_library(core STATIC
     hle/kernel/svc/svc_thread_profiler.cpp
     hle/kernel/svc/svc_tick.cpp
     hle/kernel/svc/svc_transfer_memory.cpp
+    hle/kernel/svc_common.h
+    hle/kernel/svc_results.h
+    hle/kernel/svc_types.h
     hle/result.h
     hle/service/acc/acc.cpp
     hle/service/acc/acc.h
@@ -472,6 +478,8 @@ add_library(core STATIC
     hle/service/caps/caps_types.h
     hle/service/caps/caps_u.cpp
     hle/service/caps/caps_u.h
+    hle/service/cmif_serialization.h
+    hle/service/cmif_types.h
     hle/service/erpt/erpt.cpp
     hle/service/erpt/erpt.h
     hle/service/es/es.cpp
@@ -484,14 +492,25 @@ add_library(core STATIC
     hle/service/fatal/fatal_p.h
     hle/service/fatal/fatal_u.cpp
     hle/service/fatal/fatal_u.h
+    hle/service/fgm/fgm.cpp
+    hle/service/fgm/fgm.h
     hle/service/filesystem/filesystem.cpp
     hle/service/filesystem/filesystem.h
-    hle/service/filesystem/fsp_ldr.cpp
-    hle/service/filesystem/fsp_ldr.h
-    hle/service/filesystem/fsp_pr.cpp
-    hle/service/filesystem/fsp_pr.h
-    hle/service/filesystem/fsp_srv.cpp
-    hle/service/filesystem/fsp_srv.h
+    hle/service/filesystem/fsp/fs_i_directory.cpp
+    hle/service/filesystem/fsp/fs_i_directory.h
+    hle/service/filesystem/fsp/fs_i_file.cpp
+    hle/service/filesystem/fsp/fs_i_file.h
+    hle/service/filesystem/fsp/fs_i_filesystem.cpp
+    hle/service/filesystem/fsp/fs_i_filesystem.h
+    hle/service/filesystem/fsp/fs_i_storage.cpp
+    hle/service/filesystem/fsp/fs_i_storage.h
+    hle/service/filesystem/fsp/fsp_ldr.cpp
+    hle/service/filesystem/fsp/fsp_ldr.h
+    hle/service/filesystem/fsp/fsp_pr.cpp
+    hle/service/filesystem/fsp/fsp_pr.h
+    hle/service/filesystem/fsp/fsp_srv.cpp
+    hle/service/filesystem/fsp/fsp_srv.h
+    hle/service/filesystem/fsp/fsp_util.h
     hle/service/filesystem/romfs_controller.cpp
     hle/service/filesystem/romfs_controller.h
     hle/service/filesystem/save_data_controller.cpp
@@ -549,13 +568,18 @@ add_library(core STATIC
     hle/service/hid/irs.h
     hle/service/hid/xcd.cpp
     hle/service/hid/xcd.h
+    hle/service/hle_ipc.cpp
+    hle/service/hle_ipc.h
+    hle/service/ipc_helpers.h
+    hle/service/kernel_helpers.cpp
+    hle/service/kernel_helpers.h
     hle/service/lbl/lbl.cpp
     hle/service/lbl/lbl.h
     hle/service/ldn/lan_discovery.cpp
     hle/service/ldn/lan_discovery.h
-    hle/service/ldn/ldn_results.h
     hle/service/ldn/ldn.cpp
     hle/service/ldn/ldn.h
+    hle/service/ldn/ldn_results.h
     hle/service/ldn/ldn_types.h
     hle/service/ldr/ldr.cpp
     hle/service/ldr/ldr.h
@@ -563,16 +587,6 @@ add_library(core STATIC
     hle/service/lm/lm.h
     hle/service/mig/mig.cpp
     hle/service/mig/mig.h
-    hle/service/mii/types/char_info.cpp
-    hle/service/mii/types/char_info.h
-    hle/service/mii/types/core_data.cpp
-    hle/service/mii/types/core_data.h
-    hle/service/mii/types/raw_data.cpp
-    hle/service/mii/types/raw_data.h
-    hle/service/mii/types/store_data.cpp
-    hle/service/mii/types/store_data.h
-    hle/service/mii/types/ver3_store_data.cpp
-    hle/service/mii/types/ver3_store_data.h
     hle/service/mii/mii.cpp
     hle/service/mii/mii.h
     hle/service/mii/mii_database.cpp
@@ -584,10 +598,22 @@ add_library(core STATIC
     hle/service/mii/mii_result.h
     hle/service/mii/mii_types.h
     hle/service/mii/mii_util.h
+    hle/service/mii/types/char_info.cpp
+    hle/service/mii/types/char_info.h
+    hle/service/mii/types/core_data.cpp
+    hle/service/mii/types/core_data.h
+    hle/service/mii/types/raw_data.cpp
+    hle/service/mii/types/raw_data.h
+    hle/service/mii/types/store_data.cpp
+    hle/service/mii/types/store_data.h
+    hle/service/mii/types/ver3_store_data.cpp
+    hle/service/mii/types/ver3_store_data.h
     hle/service/mm/mm_u.cpp
     hle/service/mm/mm_u.h
     hle/service/mnpp/mnpp_app.cpp
     hle/service/mnpp/mnpp_app.h
+    hle/service/mutex.cpp
+    hle/service/mutex.h
     hle/service/ncm/ncm.cpp
     hle/service/ncm/ncm.h
     hle/service/nfc/common/amiibo_crypto.cpp
@@ -757,19 +783,12 @@ add_library(core STATIC
     hle/service/ptm/ptm.h
     hle/service/ptm/ts.cpp
     hle/service/ptm/ts.h
-    hle/service/hle_ipc.cpp
-    hle/service/hle_ipc.h
-    hle/service/ipc_helpers.h
-    hle/service/kernel_helpers.cpp
-    hle/service/kernel_helpers.h
-    hle/service/mutex.cpp
-    hle/service/mutex.h
+    hle/service/ro/ro.cpp
+    hle/service/ro/ro.h
     hle/service/ro/ro_nro_utils.cpp
     hle/service/ro/ro_nro_utils.h
     hle/service/ro/ro_results.h
     hle/service/ro/ro_types.h
-    hle/service/ro/ro.cpp
-    hle/service/ro/ro.h
     hle/service/server_manager.cpp
     hle/service/server_manager.h
     hle/service/service.cpp
@@ -836,9 +855,9 @@ add_library(core STATIC
     internal_network/network.h
     internal_network/network_interface.cpp
     internal_network/network_interface.h
-    internal_network/sockets.h
     internal_network/socket_proxy.cpp
     internal_network/socket_proxy.h
+    internal_network/sockets.h
     loader/deconstructed_rom_directory.cpp
     loader/deconstructed_rom_directory.h
     loader/kip.cpp
@@ -857,13 +876,13 @@ add_library(core STATIC
     loader/nsp.h
     loader/xci.cpp
     loader/xci.h
+    memory.cpp
+    memory.h
     memory/cheat_engine.cpp
     memory/cheat_engine.h
     memory/dmnt_cheat_types.h
     memory/dmnt_cheat_vm.cpp
     memory/dmnt_cheat_vm.h
-    memory.cpp
-    memory.h
     perf_stats.cpp
     perf_stats.h
     precompiled_headers.h
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e4b877229..ae6003629 100755
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -21,13 +21,13 @@
 #include "core/debugger/debugger.h"
 #include "core/device_memory.h"
 #include "core/file_sys/bis_factory.h"
-#include "core/file_sys/mode.h"
+#include "core/file_sys/fs_filesystem.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs_factory.h"
 #include "core/file_sys/savedata_factory.h"
-#include "core/file_sys/vfs_concat.h"
-#include "core/file_sys/vfs_real.h"
+#include "core/file_sys/vfs/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_real.h"
 #include "core/gpu_dirty_memory_manager.h"
 #include "core/hle/kernel/k_memory_manager.h"
 #include "core/hle/kernel/k_process.h"
@@ -102,7 +102,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
     Common::SplitPath(path, &dir_name, &filename, nullptr);
 
     if (filename == "00") {
-        const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
+        const auto dir = vfs->OpenDirectory(dir_name, FileSys::OpenMode::Read);
         std::vector<FileSys::VirtualFile> concat;
 
         for (u32 i = 0; i < 0x10; ++i) {
@@ -127,10 +127,10 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
     }
 
     if (Common::FS::IsDir(path)) {
-        return vfs->OpenFile(path + "/main", FileSys::Mode::Read);
+        return vfs->OpenFile(path + "/main", FileSys::OpenMode::Read);
     }
 
-    return vfs->OpenFile(path, FileSys::Mode::Read);
+    return vfs->OpenFile(path, FileSys::OpenMode::Read);
 }
 
 struct System::Impl {
diff --git a/src/core/core.h b/src/core/core.h
index 6634098d4..ad5ac97e5 100755
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -13,7 +13,7 @@
 #include <vector>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Core::Frontend {
 class EmuWindow;
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index eaab2f502..cf4cab1f1 100755
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -7,7 +7,7 @@
 #include <span>
 #include <type_traits>
 #include "common/common_types.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Core::Crypto {
 
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
index 2c46e37ea..dc4ec5e50 100755
--- a/src/core/crypto/encryption_layer.h
+++ b/src/core/crypto/encryption_layer.h
@@ -4,7 +4,7 @@
 #pragma once
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Core::Crypto {
 
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 89d2f6675..71512fb92 100755
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -21,9 +21,9 @@
 #include "core/crypto/partition_data_manager.h"
 #include "core/crypto/xts_encryption_layer.h"
 #include "core/file_sys/kernel_executable.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_offset.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/loader/loader.h"
 
 using Common::AsArray;
diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h
index 75bf8fbf2..94445b658 100755
--- a/src/core/crypto/partition_data_manager.h
+++ b/src/core/crypto/partition_data_manager.h
@@ -5,7 +5,7 @@
 
 #include <vector>
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Core::Crypto {
 
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 82d9171b3..807c29c61 100755
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -4,9 +4,8 @@
 #include <fmt/format.h>
 #include "common/fs/path_util.h"
 #include "core/file_sys/bis_factory.h"
-#include "core/file_sys/mode.h"
 #include "core/file_sys/registered_cache.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
@@ -84,7 +83,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
                                              VirtualFilesystem file_system) const {
     auto& keys = Core::Crypto::KeyManager::Instance();
     Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
-        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)};
+        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), OpenMode::Read)};
     keys.PopulateFromPartitionData(pdm);
 
     switch (id) {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 6e8ea64c5..a6c412cce 100755
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -6,7 +6,7 @@
 #include <memory>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 248002c7e..e6ed9a9d3 100755
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -13,8 +13,8 @@
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/partition_filesystem.h"
 #include "core/file_sys/submission_package.h"
-#include "core/file_sys/vfs_offset.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/loader/loader.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 2bc6749f7..f4b90cedb 100755
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -8,7 +8,7 @@
 #include <vector>
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Core::Crypto {
 class KeyManager;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index e7aa8cf87..7e543576e 100755
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -13,7 +13,7 @@
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/loader/loader.h"
 
 #include "core/file_sys/fssystem/fssystem_compression_configuration.h"
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 430505124..8cc82ccb8 100755
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -13,7 +13,7 @@
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/crypto/key_manager.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index a9b1325a2..eab578ff1 100755
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -5,7 +5,7 @@
 #include "common/string_util.h"
 #include "common/swap.h"
 #include "core/file_sys/control_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 43502df6d..c9e6adb42 100755
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -8,7 +8,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 3ae86fe4b..50f65cc5f 100755
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -7,18 +7,13 @@
 
 namespace FileSys {
 
-constexpr Result ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
-constexpr Result ERROR_PATH_ALREADY_EXISTS{ErrorModule::FS, 2};
-constexpr Result ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
-constexpr Result ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
-constexpr Result ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
-constexpr Result ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
-constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
-constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
-constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
-
+constexpr Result ResultPathNotFound{ErrorModule::FS, 1};
+constexpr Result ResultPathAlreadyExists{ErrorModule::FS, 2};
 constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50};
 constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001};
+constexpr Result ResultTargetNotFound{ErrorModule::FS, 1002};
+constexpr Result ResultPortSdCardNoDevice{ErrorModule::FS, 2001};
+constexpr Result ResultNotImplemented{ErrorModule::FS, 3001};
 constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002};
 constexpr Result ResultOutOfRange{ErrorModule::FS, 3005};
 constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294};
@@ -78,10 +73,21 @@ constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
 constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
 constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
 constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
+constexpr Result ResultUnexpectedInPathA{ErrorModule::FS, 5328};
 constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
+constexpr Result ResultInvalidPath{ErrorModule::FS, 6002};
+constexpr Result ResultTooLongPath{ErrorModule::FS, 6003};
+constexpr Result ResultInvalidCharacter{ErrorModule::FS, 6004};
+constexpr Result ResultInvalidPathFormat{ErrorModule::FS, 6005};
+constexpr Result ResultDirectoryUnobtainable{ErrorModule::FS, 6006};
+constexpr Result ResultNotNormalized{ErrorModule::FS, 6007};
 constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
 constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
 constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
+constexpr Result ResultInvalidOpenMode{ErrorModule::FS, 6072};
+constexpr Result ResultFileExtensionWithoutOpenModeAllowAppend{ErrorModule::FS, 6201};
+constexpr Result ResultReadNotPermitted{ErrorModule::FS, 6202};
+constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203};
 constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
 constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
 constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
diff --git a/src/core/file_sys/fs_directory.h b/src/core/file_sys/fs_directory.h
new file mode 100755
index 000000000..25c9cb18a
--- /dev/null
+++ b/src/core/file_sys/fs_directory.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace FileSys {
+
+constexpr inline size_t EntryNameLengthMax = 0x300;
+
+struct DirectoryEntry {
+    DirectoryEntry(std::string_view view, s8 entry_type, u64 entry_size)
+        : type{entry_type}, file_size{static_cast<s64>(entry_size)} {
+        const std::size_t copy_size = view.copy(name, std::size(name) - 1);
+        name[copy_size] = '\0';
+    }
+
+    char name[EntryNameLengthMax + 1];
+    INSERT_PADDING_BYTES(3);
+    s8 type;
+    INSERT_PADDING_BYTES(3);
+    s64 file_size;
+};
+
+static_assert(sizeof(DirectoryEntry) == 0x310,
+              "Directory Entry struct isn't exactly 0x310 bytes long!");
+static_assert(offsetof(DirectoryEntry, type) == 0x304, "Wrong offset for type in Entry.");
+static_assert(offsetof(DirectoryEntry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
+
+struct DirectoryHandle {
+    void* handle;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_file.h b/src/core/file_sys/fs_file.h
new file mode 100755
index 000000000..4fb77e8db
--- /dev/null
+++ b/src/core/file_sys/fs_file.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace FileSys {
+
+struct ReadOption {
+    u32 value;
+
+    static const ReadOption None;
+};
+
+enum ReadOptionFlag : u32 {
+    ReadOptionFlag_None = (0 << 0),
+};
+
+inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None};
+
+inline constexpr bool operator==(const ReadOption& lhs, const ReadOption& rhs) {
+    return lhs.value == rhs.value;
+}
+
+inline constexpr bool operator!=(const ReadOption& lhs, const ReadOption& rhs) {
+    return !(lhs == rhs);
+}
+
+static_assert(sizeof(ReadOption) == sizeof(u32));
+
+enum WriteOptionFlag : u32 {
+    WriteOptionFlag_None = (0 << 0),
+    WriteOptionFlag_Flush = (1 << 0),
+};
+
+struct WriteOption {
+    u32 value;
+
+    constexpr inline bool HasFlushFlag() const {
+        return value & WriteOptionFlag_Flush;
+    }
+
+    static const WriteOption None;
+    static const WriteOption Flush;
+};
+
+inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None};
+inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush};
+
+inline constexpr bool operator==(const WriteOption& lhs, const WriteOption& rhs) {
+    return lhs.value == rhs.value;
+}
+
+inline constexpr bool operator!=(const WriteOption& lhs, const WriteOption& rhs) {
+    return !(lhs == rhs);
+}
+
+static_assert(sizeof(WriteOption) == sizeof(u32));
+
+struct FileHandle {
+    void* handle;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_filesystem.h b/src/core/file_sys/fs_filesystem.h
new file mode 100755
index 000000000..7f237b7fa
--- /dev/null
+++ b/src/core/file_sys/fs_filesystem.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace FileSys {
+
+enum class OpenMode : u32 {
+    Read = (1 << 0),
+    Write = (1 << 1),
+    AllowAppend = (1 << 2),
+
+    ReadWrite = (Read | Write),
+    All = (ReadWrite | AllowAppend),
+};
+DECLARE_ENUM_FLAG_OPERATORS(OpenMode)
+
+enum class OpenDirectoryMode : u64 {
+    Directory = (1 << 0),
+    File = (1 << 1),
+
+    All = (Directory | File),
+};
+DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode)
+
+enum class DirectoryEntryType : u8 {
+    Directory = 0,
+    File = 1,
+};
+
+enum class CreateOption : u8 {
+    None = (0 << 0),
+    BigFile = (1 << 0),
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_memory_management.h b/src/core/file_sys/fs_memory_management.h
new file mode 100755
index 000000000..f03c6354b
--- /dev/null
+++ b/src/core/file_sys/fs_memory_management.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include "common/alignment.h"
+
+namespace FileSys {
+
+constexpr size_t RequiredAlignment = alignof(u64);
+
+void* AllocateUnsafe(size_t size) {
+    // Allocate
+    void* const ptr = ::operator new(size, std::align_val_t{RequiredAlignment});
+
+    // Check alignment
+    ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment));
+
+    // Return allocated pointer
+    return ptr;
+}
+
+void DeallocateUnsafe(void* ptr, size_t size) {
+    // Deallocate the pointer
+    ::operator delete(ptr, std::align_val_t{RequiredAlignment});
+}
+
+void* Allocate(size_t size) {
+    return AllocateUnsafe(size);
+}
+
+void Deallocate(void* ptr, size_t size) {
+    // If the pointer is non-null, deallocate it
+    if (ptr != nullptr) {
+        DeallocateUnsafe(ptr, size);
+    }
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_operate_range.h b/src/core/file_sys/fs_operate_range.h
new file mode 100755
index 000000000..04ea64cc0
--- /dev/null
+++ b/src/core/file_sys/fs_operate_range.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace FileSys {
+
+enum class OperationId : s64 {
+    FillZero = 0,
+    DestroySignature = 1,
+    Invalidate = 2,
+    QueryRange = 3,
+    QueryUnpreparedRange = 4,
+    QueryLazyLoadCompletionRate = 5,
+    SetLazyLoadPriority = 6,
+
+    ReadLazyLoadFileForciblyForDebug = 10001,
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_path.h b/src/core/file_sys/fs_path.h
new file mode 100755
index 000000000..56ba08a6a
--- /dev/null
+++ b/src/core/file_sys/fs_path.h
@@ -0,0 +1,566 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fs_memory_management.h"
+#include "core/file_sys/fs_path_utility.h"
+#include "core/file_sys/fs_string_util.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+class DirectoryPathParser;
+
+class Path {
+    YUZU_NON_COPYABLE(Path);
+    YUZU_NON_MOVEABLE(Path);
+
+private:
+    static constexpr const char* EmptyPath = "";
+    static constexpr size_t WriteBufferAlignmentLength = 8;
+
+private:
+    friend class DirectoryPathParser;
+
+public:
+    class WriteBuffer {
+        YUZU_NON_COPYABLE(WriteBuffer);
+
+    private:
+        char* m_buffer;
+        size_t m_length_and_is_normalized;
+
+    public:
+        constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) {}
+
+        constexpr ~WriteBuffer() {
+            if (m_buffer != nullptr) {
+                Deallocate(m_buffer, this->GetLength());
+                this->ResetBuffer();
+            }
+        }
+
+        constexpr WriteBuffer(WriteBuffer&& rhs)
+            : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
+            rhs.ResetBuffer();
+        }
+
+        constexpr WriteBuffer& operator=(WriteBuffer&& rhs) {
+            if (m_buffer != nullptr) {
+                Deallocate(m_buffer, this->GetLength());
+            }
+
+            m_buffer = rhs.m_buffer;
+            m_length_and_is_normalized = rhs.m_length_and_is_normalized;
+
+            rhs.ResetBuffer();
+
+            return *this;
+        }
+
+        constexpr void ResetBuffer() {
+            m_buffer = nullptr;
+            this->SetLength(0);
+        }
+
+        constexpr char* Get() const {
+            return m_buffer;
+        }
+
+        constexpr size_t GetLength() const {
+            return m_length_and_is_normalized >> 1;
+        }
+
+        constexpr bool IsNormalized() const {
+            return static_cast<bool>(m_length_and_is_normalized & 1);
+        }
+
+        constexpr void SetNormalized() {
+            m_length_and_is_normalized |= static_cast<size_t>(1);
+        }
+
+        constexpr void SetNotNormalized() {
+            m_length_and_is_normalized &= ~static_cast<size_t>(1);
+        }
+
+    private:
+        constexpr WriteBuffer(char* buffer, size_t length)
+            : m_buffer(buffer), m_length_and_is_normalized(0) {
+            this->SetLength(length);
+        }
+
+    public:
+        static WriteBuffer Make(size_t length) {
+            if (void* alloc = Allocate(length); alloc != nullptr) {
+                return WriteBuffer(static_cast<char*>(alloc), length);
+            } else {
+                return WriteBuffer();
+            }
+        }
+
+    private:
+        constexpr void SetLength(size_t size) {
+            m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
+        }
+    };
+
+private:
+    const char* m_str;
+    WriteBuffer m_write_buffer;
+
+public:
+    constexpr Path() : m_str(EmptyPath), m_write_buffer() {}
+
+    constexpr Path(const char* s) : m_str(s), m_write_buffer() {
+        m_write_buffer.SetNormalized();
+    }
+
+    constexpr ~Path() = default;
+
+    constexpr Result SetShallowBuffer(const char* buffer) {
+        // Check pre-conditions
+        ASSERT(m_write_buffer.GetLength() == 0);
+
+        // Check the buffer is valid
+        R_UNLESS(buffer != nullptr, ResultNullptrArgument);
+
+        // Set buffer
+        this->SetReadOnlyBuffer(buffer);
+
+        // Note that we're normalized
+        this->SetNormalized();
+
+        R_SUCCEED();
+    }
+
+    constexpr const char* GetString() const {
+        // Check pre-conditions
+        ASSERT(this->IsNormalized());
+
+        return m_str;
+    }
+
+    constexpr size_t GetLength() const {
+        if (std::is_constant_evaluated()) {
+            return Strlen(this->GetString());
+        } else {
+            return std::strlen(this->GetString());
+        }
+    }
+
+    constexpr bool IsEmpty() const {
+        return *m_str == '\x00';
+    }
+
+    constexpr bool IsMatchHead(const char* p, size_t len) const {
+        return Strncmp(this->GetString(), p, len) == 0;
+    }
+
+    Result Initialize(const Path& rhs) {
+        // Check the other path is normalized
+        const bool normalized = rhs.IsNormalized();
+        R_UNLESS(normalized, ResultNotNormalized);
+
+        // Allocate buffer for our path
+        const auto len = rhs.GetLength();
+        R_TRY(this->Preallocate(len + 1));
+
+        // Copy the path
+        const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
+        R_UNLESS(copied == len, ResultUnexpectedInPathA);
+
+        // Set normalized
+        this->SetNormalized();
+        R_SUCCEED();
+    }
+
+    Result Initialize(const char* path, size_t len) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        // Initialize
+        R_TRY(this->InitializeImpl(path, len));
+
+        // Set not normalized
+        this->SetNotNormalized();
+
+        R_SUCCEED();
+    }
+
+    Result Initialize(const char* path) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        R_RETURN(this->Initialize(path, std::strlen(path)));
+    }
+
+    Result InitializeWithReplaceBackslash(const char* path) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        // Initialize
+        R_TRY(this->InitializeImpl(path, std::strlen(path)));
+
+        // Replace slashes as desired
+        if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
+            Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
+        }
+
+        // Set not normalized
+        this->SetNotNormalized();
+
+        R_SUCCEED();
+    }
+
+    Result InitializeWithReplaceForwardSlashes(const char* path) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        // Initialize
+        R_TRY(this->InitializeImpl(path, std::strlen(path)));
+
+        // Replace slashes as desired
+        if (m_write_buffer.GetLength() > 1) {
+            if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
+                p[0] = '\\';
+                p[1] = '\\';
+            }
+        }
+
+        // Set not normalized
+        this->SetNotNormalized();
+
+        R_SUCCEED();
+    }
+
+    Result InitializeWithNormalization(const char* path, size_t size) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        // Initialize
+        R_TRY(this->InitializeImpl(path, size));
+
+        // Set not normalized
+        this->SetNotNormalized();
+
+        // Perform normalization
+        PathFlags path_flags;
+        if (IsPathRelative(m_str)) {
+            path_flags.AllowRelativePath();
+        } else if (IsWindowsPath(m_str, true)) {
+            path_flags.AllowWindowsPath();
+        } else {
+            /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then
+             * returns success. */
+            /* This seems like a bug. */
+            size_t dummy;
+            bool normalized;
+            R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy),
+                                              m_str));
+
+            this->SetNormalized();
+            R_SUCCEED();
+        }
+
+        // Normalize
+        R_TRY(this->Normalize(path_flags));
+
+        this->SetNormalized();
+        R_SUCCEED();
+    }
+
+    Result InitializeWithNormalization(const char* path) {
+        // Check the path is valid
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        R_RETURN(this->InitializeWithNormalization(path, std::strlen(path)));
+    }
+
+    Result InitializeAsEmpty() {
+        // Clear our buffer
+        this->ClearBuffer();
+
+        // Set normalized
+        this->SetNormalized();
+
+        R_SUCCEED();
+    }
+
+    Result AppendChild(const char* child) {
+        // Check the path is valid
+        R_UNLESS(child != nullptr, ResultNullptrArgument);
+
+        // Basic checks. If we have a path and the child is empty, we have nothing to do
+        const char* c = child;
+        if (m_str[0]) {
+            // Skip an early separator
+            if (*c == '/') {
+                ++c;
+            }
+
+            R_SUCCEED_IF(*c == '\x00');
+        }
+
+        // If we don't have a string, we can just initialize
+        auto cur_len = std::strlen(m_str);
+        if (cur_len == 0) {
+            R_RETURN(this->Initialize(child));
+        }
+
+        // Remove a trailing separator
+        if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') {
+            --cur_len;
+        }
+
+        // Get the child path's length
+        auto child_len = std::strlen(c);
+
+        // Reset our write buffer
+        WriteBuffer old_write_buffer;
+        if (m_write_buffer.Get() != nullptr) {
+            old_write_buffer = std::move(m_write_buffer);
+            this->ClearBuffer();
+        }
+
+        // Pre-allocate the new buffer
+        R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
+
+        // Get our write buffer
+        auto* dst = m_write_buffer.Get();
+        if (old_write_buffer.Get() != nullptr && cur_len > 0) {
+            Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
+        }
+
+        // Add separator
+        dst[cur_len] = '/';
+
+        // Copy the child path
+        const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1);
+        R_UNLESS(copied == child_len, ResultUnexpectedInPathA);
+
+        R_SUCCEED();
+    }
+
+    Result AppendChild(const Path& rhs) {
+        R_RETURN(this->AppendChild(rhs.GetString()));
+    }
+
+    Result Combine(const Path& parent, const Path& child) {
+        // Get the lengths
+        const auto p_len = parent.GetLength();
+        const auto c_len = child.GetLength();
+
+        // Allocate our buffer
+        R_TRY(this->Preallocate(p_len + c_len + 1));
+
+        // Initialize as parent
+        R_TRY(this->Initialize(parent));
+
+        // If we're empty, we can just initialize as child
+        if (this->IsEmpty()) {
+            R_TRY(this->Initialize(child));
+        } else {
+            // Otherwise, we should append the child
+            R_TRY(this->AppendChild(child));
+        }
+
+        R_SUCCEED();
+    }
+
+    Result RemoveChild() {
+        // If we don't have a write-buffer, ensure that we have one
+        if (m_write_buffer.Get() == nullptr) {
+            if (const auto len = std::strlen(m_str); len > 0) {
+                R_TRY(this->Preallocate(len));
+                Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
+            }
+        }
+
+        // Check that it's possible for us to remove a child
+        auto* p = m_write_buffer.Get();
+        s32 len = std::strlen(p);
+        R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented);
+
+        // Handle a trailing separator
+        if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) {
+            --len;
+        }
+
+        // Remove the child path segment
+        while ((--len) >= 0 && p[len]) {
+            if (p[len] == '/' || p[len] == '\\') {
+                if (len > 0) {
+                    p[len] = 0;
+                } else {
+                    p[1] = 0;
+                    len = 1;
+                }
+                break;
+            }
+        }
+
+        // Check that length remains > 0
+        R_UNLESS(len > 0, ResultNotImplemented);
+
+        R_SUCCEED();
+    }
+
+    Result Normalize(const PathFlags& flags) {
+        // If we're already normalized, nothing to do
+        R_SUCCEED_IF(this->IsNormalized());
+
+        // Check if we're normalized
+        bool normalized;
+        size_t dummy;
+        R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str,
+                                          flags));
+
+        // If we're not normalized, normalize
+        if (!normalized) {
+            // Determine necessary buffer length
+            auto len = m_write_buffer.GetLength();
+            if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) {
+                len += 2;
+            }
+            if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) {
+                len += 1;
+            }
+
+            // Allocate a new buffer
+            const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength);
+            auto buf = WriteBuffer::Make(size);
+            R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
+
+            // Normalize into it
+            R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(),
+                                           m_write_buffer.GetLength(), flags));
+
+            // Set the normalized buffer as our buffer
+            this->SetModifiableBuffer(std::move(buf));
+        }
+
+        // Set normalized
+        this->SetNormalized();
+        R_SUCCEED();
+    }
+
+private:
+    void ClearBuffer() {
+        m_write_buffer.ResetBuffer();
+        m_str = EmptyPath;
+    }
+
+    void SetModifiableBuffer(WriteBuffer&& buffer) {
+        // Check pre-conditions
+        ASSERT(buffer.Get() != nullptr);
+        ASSERT(buffer.GetLength() > 0);
+        ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
+
+        // Get whether we're normalized
+        if (m_write_buffer.IsNormalized()) {
+            buffer.SetNormalized();
+        } else {
+            buffer.SetNotNormalized();
+        }
+
+        // Set write buffer
+        m_write_buffer = std::move(buffer);
+        m_str = m_write_buffer.Get();
+    }
+
+    constexpr void SetReadOnlyBuffer(const char* buffer) {
+        m_str = buffer;
+        m_write_buffer.ResetBuffer();
+    }
+
+    Result Preallocate(size_t length) {
+        // Allocate additional space, if needed
+        if (length > m_write_buffer.GetLength()) {
+            // Allocate buffer
+            const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength);
+            auto buf = WriteBuffer::Make(size);
+            R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
+
+            // Set write buffer
+            this->SetModifiableBuffer(std::move(buf));
+        }
+
+        R_SUCCEED();
+    }
+
+    Result InitializeImpl(const char* path, size_t size) {
+        if (size > 0 && path[0]) {
+            // Pre allocate a buffer for the path
+            R_TRY(this->Preallocate(size + 1));
+
+            // Copy the path
+            const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
+            R_UNLESS(copied >= size, ResultUnexpectedInPathA);
+        } else {
+            // We can just clear the buffer
+            this->ClearBuffer();
+        }
+
+        R_SUCCEED();
+    }
+
+    constexpr char* GetWriteBuffer() {
+        ASSERT(m_write_buffer.Get() != nullptr);
+        return m_write_buffer.Get();
+    }
+
+    constexpr size_t GetWriteBufferLength() const {
+        return m_write_buffer.GetLength();
+    }
+
+    constexpr bool IsNormalized() const {
+        return m_write_buffer.IsNormalized();
+    }
+
+    constexpr void SetNormalized() {
+        m_write_buffer.SetNormalized();
+    }
+
+    constexpr void SetNotNormalized() {
+        m_write_buffer.SetNotNormalized();
+    }
+
+public:
+    bool operator==(const FileSys::Path& rhs) const {
+        return std::strcmp(this->GetString(), rhs.GetString()) == 0;
+    }
+    bool operator!=(const FileSys::Path& rhs) const {
+        return !(*this == rhs);
+    }
+    bool operator==(const char* p) const {
+        return std::strcmp(this->GetString(), p) == 0;
+    }
+    bool operator!=(const char* p) const {
+        return !(*this == p);
+    }
+};
+
+inline Result SetUpFixedPath(FileSys::Path* out, const char* s) {
+    // Verify the path is normalized
+    bool normalized;
+    size_t dummy;
+    R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s));
+
+    R_UNLESS(normalized, ResultInvalidPathFormat);
+
+    // Set the fixed path
+    R_RETURN(out->SetShallowBuffer(s));
+}
+
+constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) {
+    const char* const str = path.GetString();
+    return IsWindowsDrive(str) &&
+           (str[2] == StringTraits::DirectorySeparator ||
+            str[2] == StringTraits::AlternateDirectorySeparator) &&
+           str[3] == StringTraits::NullTerminator;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_path_utility.h b/src/core/file_sys/fs_path_utility.h
new file mode 100755
index 000000000..e9011d065
--- /dev/null
+++ b/src/core/file_sys/fs_path_utility.h
@@ -0,0 +1,1239 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/scope_exit.h"
+#include "core/file_sys/fs_directory.h"
+#include "core/file_sys/fs_memory_management.h"
+#include "core/file_sys/fs_string_util.h"
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+constexpr inline size_t MountNameLengthMax = 15;
+
+namespace StringTraits {
+
+constexpr inline char DirectorySeparator = '/';
+constexpr inline char DriveSeparator = ':';
+constexpr inline char Dot = '.';
+constexpr inline char NullTerminator = '\x00';
+
+constexpr inline char AlternateDirectorySeparator = '\\';
+
+constexpr inline const char InvalidCharacters[6] = {':', '*', '?', '<', '>', '|'};
+constexpr inline const char InvalidCharactersForHostName[6] = {':', '*', '<', '>', '|', '$'};
+constexpr inline const char InvalidCharactersForMountName[5] = {'*', '?', '<', '>', '|'};
+
+namespace impl {
+
+template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
+consteval u64 MakeInvalidCharacterMask(size_t n) {
+    u64 mask = 0;
+    for (size_t i = 0; i < NumInvalidCharacters; ++i) {
+        if ((static_cast<u64>(InvalidCharacterSet[i]) >> 6) == n) {
+            mask |= static_cast<u64>(1) << (static_cast<u64>(InvalidCharacterSet[i]) & 0x3F);
+        }
+    }
+    return mask;
+}
+
+template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
+constexpr bool IsInvalidCharacterImpl(char c) {
+    constexpr u64 Masks[4] = {
+        MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(0),
+        MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(1),
+        MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(2),
+        MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(3)};
+
+    return (Masks[static_cast<u64>(c) >> 6] &
+            (static_cast<u64>(1) << (static_cast<u64>(c) & 0x3F))) != 0;
+}
+
+} // namespace impl
+
+constexpr bool IsInvalidCharacter(char c) {
+    return impl::IsInvalidCharacterImpl<InvalidCharacters, Common::Size(InvalidCharacters)>(c);
+}
+constexpr bool IsInvalidCharacterForHostName(char c) {
+    return impl::IsInvalidCharacterImpl<InvalidCharactersForHostName,
+                                        Common::Size(InvalidCharactersForHostName)>(c);
+}
+constexpr bool IsInvalidCharacterForMountName(char c) {
+    return impl::IsInvalidCharacterImpl<InvalidCharactersForMountName,
+                                        Common::Size(InvalidCharactersForMountName)>(c);
+}
+
+} // namespace StringTraits
+
+constexpr inline size_t WindowsDriveLength = 2;
+constexpr inline size_t UncPathPrefixLength = 2;
+constexpr inline size_t DosDevicePathPrefixLength = 4;
+
+class PathFlags {
+private:
+    static constexpr u32 WindowsPathFlag = (1 << 0);
+    static constexpr u32 RelativePathFlag = (1 << 1);
+    static constexpr u32 EmptyPathFlag = (1 << 2);
+    static constexpr u32 MountNameFlag = (1 << 3);
+    static constexpr u32 BackslashFlag = (1 << 4);
+    static constexpr u32 AllCharactersFlag = (1 << 5);
+
+private:
+    u32 m_value;
+
+public:
+    constexpr PathFlags() : m_value(0) { /* ... */
+    }
+
+#define DECLARE_PATH_FLAG_HANDLER(__WHICH__)                                                       \
+    constexpr bool Is##__WHICH__##Allowed() const { return (m_value & __WHICH__##Flag) != 0; }     \
+    constexpr void Allow##__WHICH__() { m_value |= __WHICH__##Flag; }
+
+    DECLARE_PATH_FLAG_HANDLER(WindowsPath)
+    DECLARE_PATH_FLAG_HANDLER(RelativePath)
+    DECLARE_PATH_FLAG_HANDLER(EmptyPath)
+    DECLARE_PATH_FLAG_HANDLER(MountName)
+    DECLARE_PATH_FLAG_HANDLER(Backslash)
+    DECLARE_PATH_FLAG_HANDLER(AllCharacters)
+
+#undef DECLARE_PATH_FLAG_HANDLER
+};
+
+template <typename T>
+    requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
+constexpr inline bool IsDosDevicePath(const T* path) {
+    ASSERT(path != nullptr);
+
+    using namespace StringTraits;
+
+    return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator &&
+           (path[2] == Dot || path[2] == '?') &&
+           (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator);
+}
+
+template <typename T>
+    requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
+constexpr inline bool IsUncPath(const T* path, bool allow_forward_slash = true,
+                                bool allow_back_slash = true) {
+    ASSERT(path != nullptr);
+
+    using namespace StringTraits;
+
+    return (allow_forward_slash && path[0] == DirectorySeparator &&
+            path[1] == DirectorySeparator) ||
+           (allow_back_slash && path[0] == AlternateDirectorySeparator &&
+            path[1] == AlternateDirectorySeparator);
+}
+
+constexpr inline bool IsWindowsDrive(const char* path) {
+    ASSERT(path != nullptr);
+
+    return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) &&
+           path[1] == StringTraits::DriveSeparator;
+}
+
+constexpr inline bool IsWindowsPath(const char* path, bool allow_forward_slash_unc) {
+    return IsWindowsDrive(path) || IsDosDevicePath(path) ||
+           IsUncPath(path, allow_forward_slash_unc, true);
+}
+
+constexpr inline int GetWindowsSkipLength(const char* path) {
+    if (IsDosDevicePath(path)) {
+        return DosDevicePathPrefixLength;
+    } else if (IsWindowsDrive(path)) {
+        return WindowsDriveLength;
+    } else if (IsUncPath(path)) {
+        return UncPathPrefixLength;
+    } else {
+        return 0;
+    }
+}
+
+constexpr inline bool IsPathAbsolute(const char* path) {
+    return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator;
+}
+
+constexpr inline bool IsPathRelative(const char* path) {
+    return path[0] && !IsPathAbsolute(path);
+}
+
+constexpr inline bool IsCurrentDirectory(const char* path) {
+    return path[0] == StringTraits::Dot &&
+           (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator);
+}
+
+constexpr inline bool IsParentDirectory(const char* path) {
+    return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot &&
+           (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator);
+}
+
+constexpr inline bool IsPathStartWithCurrentDirectory(const char* path) {
+    return IsCurrentDirectory(path) || IsParentDirectory(path);
+}
+
+constexpr inline bool IsSubPath(const char* lhs, const char* rhs) {
+    // Check pre-conditions
+    ASSERT(lhs != nullptr);
+    ASSERT(rhs != nullptr);
+
+    // Import StringTraits names for current scope
+    using namespace StringTraits;
+
+    // Special case certain paths
+    if (IsUncPath(lhs) && !IsUncPath(rhs)) {
+        return false;
+    }
+    if (!IsUncPath(lhs) && IsUncPath(rhs)) {
+        return false;
+    }
+
+    if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator &&
+        rhs[1] != NullTerminator) {
+        return true;
+    }
+    if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator &&
+        lhs[1] != NullTerminator) {
+        return true;
+    }
+
+    // Check subpath
+    for (size_t i = 0; /* ... */; ++i) {
+        if (lhs[i] == NullTerminator) {
+            return rhs[i] == DirectorySeparator;
+        } else if (rhs[i] == NullTerminator) {
+            return lhs[i] == DirectorySeparator;
+        } else if (lhs[i] != rhs[i]) {
+            return false;
+        }
+    }
+}
+
+// Path utilities
+constexpr inline void Replace(char* dst, size_t dst_size, char old_char, char new_char) {
+    ASSERT(dst != nullptr);
+    for (char* cur = dst; cur < dst + dst_size && *cur; ++cur) {
+        if (*cur == old_char) {
+            *cur = new_char;
+        }
+    }
+}
+
+constexpr inline Result CheckUtf8(const char* s) {
+    // Check pre-conditions
+    ASSERT(s != nullptr);
+
+    // Iterate, checking for utf8-validity
+    while (*s) {
+        char utf8_buf[4] = {};
+
+        const auto pick_res = PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s));
+        R_UNLESS(pick_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
+
+        u32 dummy;
+        const auto cvt_res = ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf);
+        R_UNLESS(cvt_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
+    }
+
+    R_SUCCEED();
+}
+
+// Path formatting
+class PathNormalizer {
+private:
+    enum class PathState {
+        Start,
+        Normal,
+        FirstSeparator,
+        Separator,
+        CurrentDir,
+        ParentDir,
+    };
+
+private:
+    static constexpr void ReplaceParentDirectoryPath(char* dst, const char* src) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Start with a dir-separator
+        dst[0] = DirectorySeparator;
+
+        auto i = 1;
+        while (src[i] != NullTerminator) {
+            if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) &&
+                src[i + 0] == Dot && src[i + 1] == Dot &&
+                (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) {
+                dst[i - 1] = DirectorySeparator;
+                dst[i + 0] = Dot;
+                dst[i + 1] = Dot;
+                dst[i + 2] = DirectorySeparator;
+                i += 3;
+            } else {
+                if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot &&
+                    src[i + 1] == Dot && src[i + 2] == NullTerminator) {
+                    dst[i - 1] = DirectorySeparator;
+                    dst[i + 0] = Dot;
+                    dst[i + 1] = Dot;
+                    i += 2;
+                    break;
+                }
+
+                dst[i] = src[i];
+                ++i;
+            }
+        }
+
+        dst[i] = StringTraits::NullTerminator;
+    }
+
+public:
+    static constexpr bool IsParentDirectoryPathReplacementNeeded(const char* path) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) {
+            return false;
+        }
+
+        // Check to find a parent reference using alternate separators
+        if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) {
+            size_t i;
+            for (i = 0; path[i + 3] != NullTerminator; ++path) {
+                if (path[i + 1] != Dot || path[i + 2] != Dot) {
+                    continue;
+                }
+
+                const char c0 = path[i + 0];
+                const char c3 = path[i + 3];
+
+                if (c0 == AlternateDirectorySeparator &&
+                    (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator ||
+                     c3 == NullTerminator)) {
+                    return true;
+                }
+
+                if (c3 == AlternateDirectorySeparator &&
+                    (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) {
+                    return true;
+                }
+            }
+
+            if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot &&
+                path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
+                                         bool allow_all_characters = false) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Parse the path
+        auto state = PathState::Start;
+        size_t len = 0;
+        while (path[len] != NullTerminator) {
+            // Get the current character
+            const char c = path[len++];
+
+            // Check the current character is valid
+            if (!allow_all_characters && state != PathState::Start) {
+                R_UNLESS(!IsInvalidCharacter(c), ResultInvalidCharacter);
+            }
+
+            // Process depending on current state
+            switch (state) {
+                // Import the PathState enums for convenience
+                using enum PathState;
+
+            case Start:
+                R_UNLESS(c == DirectorySeparator, ResultInvalidPathFormat);
+                state = FirstSeparator;
+                break;
+            case Normal:
+                if (c == DirectorySeparator) {
+                    state = Separator;
+                }
+                break;
+            case FirstSeparator:
+            case Separator:
+                if (c == DirectorySeparator) {
+                    *out = false;
+                    R_SUCCEED();
+                }
+
+                if (c == Dot) {
+                    state = CurrentDir;
+                } else {
+                    state = Normal;
+                }
+                break;
+            case CurrentDir:
+                if (c == DirectorySeparator) {
+                    *out = false;
+                    R_SUCCEED();
+                }
+
+                if (c == Dot) {
+                    state = ParentDir;
+                } else {
+                    state = Normal;
+                }
+                break;
+            case ParentDir:
+                if (c == DirectorySeparator) {
+                    *out = false;
+                    R_SUCCEED();
+                }
+
+                state = Normal;
+                break;
+            default:
+                UNREACHABLE();
+                break;
+            }
+        }
+
+        // Check the final state
+        switch (state) {
+            // Import the PathState enums for convenience
+            using enum PathState;
+        case Start:
+            R_THROW(ResultInvalidPathFormat);
+        case Normal:
+        case FirstSeparator:
+            *out = true;
+            break;
+        case Separator:
+        case CurrentDir:
+        case ParentDir:
+            *out = false;
+            break;
+        default:
+            UNREACHABLE();
+            break;
+        }
+
+        // Set the output length
+        *out_len = len;
+        R_SUCCEED();
+    }
+
+    static Result Normalize(char* dst, size_t* out_len, const char* path, size_t max_out_size,
+                            bool is_windows_path, bool is_drive_relative_path,
+                            bool allow_all_characters = false) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Prepare to iterate
+        const char* cur_path = path;
+        size_t total_len = 0;
+
+        // If path begins with a separator, check that we're not drive relative
+        if (cur_path[0] != DirectorySeparator) {
+            R_UNLESS(is_drive_relative_path, ResultInvalidPathFormat);
+
+            dst[total_len++] = DirectorySeparator;
+        }
+
+        // We're going to need to do path replacement, potentially
+        char* replacement_path = nullptr;
+        size_t replacement_path_size = 0;
+
+        SCOPE_EXIT({
+            if (replacement_path != nullptr) {
+                if (std::is_constant_evaluated()) {
+                    delete[] replacement_path;
+                } else {
+                    Deallocate(replacement_path, replacement_path_size);
+                }
+            }
+        });
+
+        // Perform path replacement, if necessary
+        if (IsParentDirectoryPathReplacementNeeded(cur_path)) {
+            if (std::is_constant_evaluated()) {
+                replacement_path_size = EntryNameLengthMax + 1;
+                replacement_path = new char[replacement_path_size];
+            } else {
+                replacement_path_size = EntryNameLengthMax + 1;
+                replacement_path = static_cast<char*>(Allocate(replacement_path_size));
+            }
+
+            ReplaceParentDirectoryPath(replacement_path, cur_path);
+
+            cur_path = replacement_path;
+        }
+
+        // Iterate, normalizing path components
+        bool skip_next_sep = false;
+        size_t i = 0;
+
+        while (cur_path[i] != NullTerminator) {
+            // Process a directory separator, if we run into one
+            if (cur_path[i] == DirectorySeparator) {
+                // Swallow separators
+                do {
+                    ++i;
+                } while (cur_path[i] == DirectorySeparator);
+
+                // Check if we hit end of string
+                if (cur_path[i] == NullTerminator) {
+                    break;
+                }
+
+                // If we aren't skipping the separator, write it, checking that we remain in bounds.
+                if (!skip_next_sep) {
+                    if (total_len + 1 == max_out_size) {
+                        dst[total_len] = NullTerminator;
+                        *out_len = total_len;
+                        R_THROW(ResultTooLongPath);
+                    }
+
+                    dst[total_len++] = DirectorySeparator;
+                }
+
+                // Don't skip the next separator
+                skip_next_sep = false;
+            }
+
+            // Get the length of the current directory component
+            size_t dir_len = 0;
+            while (cur_path[i + dir_len] != DirectorySeparator &&
+                   cur_path[i + dir_len] != NullTerminator) {
+                // Check for validity
+                if (!allow_all_characters) {
+                    R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), ResultInvalidCharacter);
+                }
+
+                ++dir_len;
+            }
+
+            // Handle the current dir component
+            if (IsCurrentDirectory(cur_path + i)) {
+                skip_next_sep = true;
+            } else if (IsParentDirectory(cur_path + i)) {
+                // We should have just written a separator
+                ASSERT(dst[total_len - 1] == DirectorySeparator);
+
+                // We should have started with a separator, for non-windows paths
+                if (!is_windows_path) {
+                    ASSERT(dst[0] == DirectorySeparator);
+                }
+
+                // Remove the previous component
+                if (total_len == 1) {
+                    R_UNLESS(is_windows_path, ResultDirectoryUnobtainable);
+
+                    --total_len;
+                } else {
+                    total_len -= 2;
+
+                    do {
+                        if (dst[total_len] == DirectorySeparator) {
+                            break;
+                        }
+                    } while ((--total_len) != 0);
+                }
+
+                // We should be pointing to a directory separator, for non-windows paths
+                if (!is_windows_path) {
+                    ASSERT(dst[total_len] == DirectorySeparator);
+                }
+
+                // We should remain in bounds
+                ASSERT(total_len < max_out_size);
+            } else {
+                // Copy, possibly truncating
+                if (total_len + dir_len + 1 > max_out_size) {
+                    const size_t copy_len = max_out_size - (total_len + 1);
+
+                    for (size_t j = 0; j < copy_len; ++j) {
+                        dst[total_len++] = cur_path[i + j];
+                    }
+
+                    dst[total_len] = NullTerminator;
+                    *out_len = total_len;
+                    R_THROW(ResultTooLongPath);
+                }
+
+                for (size_t j = 0; j < dir_len; ++j) {
+                    dst[total_len++] = cur_path[i + j];
+                }
+            }
+
+            // Advance past the current directory component
+            i += dir_len;
+        }
+
+        if (skip_next_sep) {
+            --total_len;
+        }
+
+        if (total_len == 0 && max_out_size != 0) {
+            total_len = 1;
+            dst[0] = DirectorySeparator;
+        }
+
+        // NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null
+        // terminator.
+        R_UNLESS(max_out_size >= total_len - 1, ResultTooLongPath);
+
+        dst[total_len] = NullTerminator;
+
+        // Check that the result path is normalized
+        bool is_normalized;
+        size_t dummy;
+        R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst,
+                           allow_all_characters));
+
+        // Assert that the result path is normalized
+        ASSERT(is_normalized);
+
+        // Set the output length
+        *out_len = total_len;
+        R_SUCCEED();
+    }
+};
+
+class PathFormatter {
+private:
+    static constexpr Result CheckSharedName(const char* name, size_t len) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        if (len == 1) {
+            R_UNLESS(name[0] != Dot, ResultInvalidPathFormat);
+        } else if (len == 2) {
+            R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
+        }
+
+        for (size_t i = 0; i < len; ++i) {
+            R_UNLESS(!IsInvalidCharacter(name[i]), ResultInvalidCharacter);
+        }
+
+        R_SUCCEED();
+    }
+
+    static constexpr Result CheckHostName(const char* name, size_t len) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        if (len == 2) {
+            R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
+        }
+
+        for (size_t i = 0; i < len; ++i) {
+            R_UNLESS(!IsInvalidCharacterForHostName(name[i]), ResultInvalidCharacter);
+        }
+
+        R_SUCCEED();
+    }
+
+    static constexpr Result CheckInvalidBackslash(bool* out_contains_backslash, const char* path,
+                                                  bool allow_backslash) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Default to no backslashes, so we can just write if we see one
+        *out_contains_backslash = false;
+
+        while (*path != NullTerminator) {
+            if (*(path++) == AlternateDirectorySeparator) {
+                *out_contains_backslash = true;
+
+                R_UNLESS(allow_backslash, ResultInvalidCharacter);
+            }
+        }
+
+        R_SUCCEED();
+    }
+
+public:
+    static constexpr Result CheckPathFormat(const char* path, const PathFlags& flags) {
+        bool normalized;
+        size_t len;
+        R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags));
+    }
+
+    static constexpr Result SkipMountName(const char** out, size_t* out_len, const char* path) {
+        R_RETURN(ParseMountName(out, out_len, nullptr, 0, path));
+    }
+
+    static constexpr Result ParseMountName(const char** out, size_t* out_len, char* out_mount_name,
+                                           size_t out_mount_name_buffer_size, const char* path) {
+        // Check pre-conditions
+        ASSERT(path != nullptr);
+        ASSERT(out_len != nullptr);
+        ASSERT(out != nullptr);
+        ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0));
+
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Determine max mount length
+        const auto max_mount_len =
+            out_mount_name_buffer_size == 0
+                ? MountNameLengthMax + 1
+                : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size);
+
+        // Parse the path until we see a drive separator
+        size_t mount_len = 0;
+        for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) {
+            const char c = path[mount_len];
+
+            // If we see a drive separator, advance, then we're done with the pre-drive separator
+            // part of the mount.
+            if (c == DriveSeparator) {
+                ++mount_len;
+                break;
+            }
+
+            // If we see a directory separator, we're not in a mount name
+            if (c == DirectorySeparator || c == AlternateDirectorySeparator) {
+                *out = path;
+                *out_len = 0;
+                R_SUCCEED();
+            }
+        }
+
+        // Check to be sure we're actually looking at a mount name
+        if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) {
+            *out = path;
+            *out_len = 0;
+            R_SUCCEED();
+        }
+
+        // Check that all characters in the mount name are allowable
+        for (size_t i = 0; i < mount_len; ++i) {
+            R_UNLESS(!IsInvalidCharacterForMountName(path[i]), ResultInvalidCharacter);
+        }
+
+        // Copy out the mount name
+        if (out_mount_name_buffer_size > 0) {
+            R_UNLESS(mount_len < out_mount_name_buffer_size, ResultTooLongPath);
+
+            for (size_t i = 0; i < mount_len; ++i) {
+                out_mount_name[i] = path[i];
+            }
+            out_mount_name[mount_len] = NullTerminator;
+        }
+
+        // Set the output
+        *out = path + mount_len;
+        *out_len = mount_len;
+        R_SUCCEED();
+    }
+
+    static constexpr Result SkipRelativeDotPath(const char** out, size_t* out_len,
+                                                const char* path) {
+        R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path));
+    }
+
+    static constexpr Result ParseRelativeDotPath(const char** out, size_t* out_len,
+                                                 char* out_relative,
+                                                 size_t out_relative_buffer_size,
+                                                 const char* path) {
+        // Check pre-conditions
+        ASSERT(path != nullptr);
+        ASSERT(out_len != nullptr);
+        ASSERT(out != nullptr);
+        ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0));
+
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Initialize the output buffer, if we have one
+        if (out_relative_buffer_size > 0) {
+            out_relative[0] = NullTerminator;
+        }
+
+        // Check if the path is relative
+        if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator ||
+                               path[1] == AlternateDirectorySeparator)) {
+            if (out_relative_buffer_size > 0) {
+                R_UNLESS(out_relative_buffer_size >= 2, ResultTooLongPath);
+
+                out_relative[0] = Dot;
+                out_relative[1] = NullTerminator;
+            }
+
+            *out = path + 1;
+            *out_len = 1;
+            R_SUCCEED();
+        }
+
+        // Ensure the path isn't a parent directory
+        R_UNLESS(!(path[0] == Dot && path[1] == Dot), ResultDirectoryUnobtainable);
+
+        // There was no relative dot path
+        *out = path;
+        *out_len = 0;
+        R_SUCCEED();
+    }
+
+    static constexpr Result SkipWindowsPath(const char** out, size_t* out_len, bool* out_normalized,
+                                            const char* path, bool has_mount_name) {
+        // We're normalized if and only if the parsing doesn't throw ResultNotNormalized()
+        *out_normalized = true;
+
+        R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) {
+            R_CATCH(ResultNotNormalized) {
+                *out_normalized = false;
+            }
+        }
+        R_END_TRY_CATCH;
+        ON_RESULT_INCLUDED(ResultNotNormalized) {
+            *out_normalized = false;
+        };
+
+        R_SUCCEED();
+    }
+
+    static constexpr Result ParseWindowsPath(const char** out, size_t* out_len, char* out_win,
+                                             size_t out_win_buffer_size, const char* path,
+                                             bool has_mount_name) {
+        // Check pre-conditions
+        ASSERT(path != nullptr);
+        ASSERT(out_len != nullptr);
+        ASSERT(out != nullptr);
+        ASSERT((out_win == nullptr) == (out_win_buffer_size == 0));
+
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Initialize the output buffer, if we have one
+        if (out_win_buffer_size > 0) {
+            out_win[0] = NullTerminator;
+        }
+
+        // Handle path start
+        const char* cur_path = path;
+        if (has_mount_name && path[0] == DirectorySeparator) {
+            if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) {
+                R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
+
+                ++cur_path;
+            } else if (IsWindowsDrive(path + 1)) {
+                R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
+
+                ++cur_path;
+            }
+        }
+
+        // Handle windows drive
+        if (IsWindowsDrive(cur_path)) {
+            // Parse up to separator
+            size_t win_path_len = WindowsDriveLength;
+            for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) {
+                R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), ResultInvalidCharacter);
+
+                if (cur_path[win_path_len] == DirectorySeparator ||
+                    cur_path[win_path_len] == AlternateDirectorySeparator) {
+                    break;
+                }
+            }
+
+            // Ensure that we're normalized, if we're required to be
+            if (out_win_buffer_size == 0) {
+                for (size_t i = 0; i < win_path_len; ++i) {
+                    R_UNLESS(cur_path[i] != AlternateDirectorySeparator, ResultNotNormalized);
+                }
+            } else {
+                // Ensure we can copy into the normalized buffer
+                R_UNLESS(win_path_len < out_win_buffer_size, ResultTooLongPath);
+
+                for (size_t i = 0; i < win_path_len; ++i) {
+                    out_win[i] = cur_path[i];
+                }
+                out_win[win_path_len] = NullTerminator;
+
+                Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator);
+            }
+
+            *out = cur_path + win_path_len;
+            *out_len = win_path_len;
+            R_SUCCEED();
+        }
+
+        // Handle DOS device
+        if (IsDosDevicePath(cur_path)) {
+            size_t dos_prefix_len = DosDevicePathPrefixLength;
+
+            if (IsWindowsDrive(cur_path + dos_prefix_len)) {
+                dos_prefix_len += WindowsDriveLength;
+            } else {
+                --dos_prefix_len;
+            }
+
+            if (out_win_buffer_size > 0) {
+                // Ensure we can copy into the normalized buffer
+                R_UNLESS(dos_prefix_len < out_win_buffer_size, ResultTooLongPath);
+
+                for (size_t i = 0; i < dos_prefix_len; ++i) {
+                    out_win[i] = cur_path[i];
+                }
+                out_win[dos_prefix_len] = NullTerminator;
+
+                Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
+            }
+
+            *out = cur_path + dos_prefix_len;
+            *out_len = dos_prefix_len;
+            R_SUCCEED();
+        }
+
+        // Handle UNC path
+        if (IsUncPath(cur_path, false, true)) {
+            const char* final_path = cur_path;
+
+            R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, ResultInvalidPathFormat);
+            R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator,
+                     ResultInvalidPathFormat);
+
+            size_t cur_component_offset = 0;
+            size_t pos = UncPathPrefixLength;
+            for (/* ... */; cur_path[pos] != NullTerminator; ++pos) {
+                if (cur_path[pos] == DirectorySeparator ||
+                    cur_path[pos] == AlternateDirectorySeparator) {
+                    if (cur_component_offset != 0) {
+                        R_TRY(CheckSharedName(cur_path + cur_component_offset,
+                                              pos - cur_component_offset));
+
+                        final_path = cur_path + pos;
+                        break;
+                    }
+
+                    R_UNLESS(cur_path[pos + 1] != DirectorySeparator, ResultInvalidPathFormat);
+                    R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator,
+                             ResultInvalidPathFormat);
+
+                    R_TRY(CheckHostName(cur_path + 2, pos - 2));
+
+                    cur_component_offset = pos + 1;
+                }
+            }
+
+            R_UNLESS(cur_component_offset != pos, ResultInvalidPathFormat);
+
+            if (cur_component_offset != 0 && final_path == cur_path) {
+                R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset));
+
+                final_path = cur_path + pos;
+            }
+
+            size_t unc_prefix_len = final_path - cur_path;
+
+            // Ensure that we're normalized, if we're required to be
+            if (out_win_buffer_size == 0) {
+                for (size_t i = 0; i < unc_prefix_len; ++i) {
+                    R_UNLESS(cur_path[i] != DirectorySeparator, ResultNotNormalized);
+                }
+            } else {
+                // Ensure we can copy into the normalized buffer
+                R_UNLESS(unc_prefix_len < out_win_buffer_size, ResultTooLongPath);
+
+                for (size_t i = 0; i < unc_prefix_len; ++i) {
+                    out_win[i] = cur_path[i];
+                }
+                out_win[unc_prefix_len] = NullTerminator;
+
+                Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
+            }
+
+            *out = cur_path + unc_prefix_len;
+            *out_len = unc_prefix_len;
+            R_SUCCEED();
+        }
+
+        // There's no windows path to parse
+        *out = path;
+        *out_len = 0;
+        R_SUCCEED();
+    }
+
+    static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
+                                         const PathFlags& flags = {}) {
+        // Ensure nothing is null
+        R_UNLESS(out != nullptr, ResultNullptrArgument);
+        R_UNLESS(out_len != nullptr, ResultNullptrArgument);
+        R_UNLESS(path != nullptr, ResultNullptrArgument);
+
+        // Verify that the path is valid utf-8
+        R_TRY(CheckUtf8(path));
+
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Handle the case where the path is empty
+        if (path[0] == NullTerminator) {
+            R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
+
+            *out = true;
+            *out_len = 0;
+            R_SUCCEED();
+        }
+
+        // All normalized paths start with a directory separator...unless they're windows paths,
+        // relative paths, or have mount names.
+        if (path[0] != DirectorySeparator) {
+            R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() ||
+                         flags.IsMountNameAllowed(),
+                     ResultInvalidPathFormat);
+        }
+
+        // Check that the path is allowed to be a windows path, if it is
+        if (IsWindowsPath(path, false)) {
+            R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
+        }
+
+        // Skip past the mount name, if one is present
+        size_t total_len = 0;
+        size_t mount_name_len = 0;
+        R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path));
+
+        // If we had a mount name, check that that was allowed
+        if (mount_name_len > 0) {
+            R_UNLESS(flags.IsMountNameAllowed(), ResultInvalidPathFormat);
+
+            total_len += mount_name_len;
+        }
+
+        // Check that the path starts as a normalized path should
+        if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) &&
+            !IsWindowsPath(path, false)) {
+            R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
+            R_UNLESS(!IsInvalidCharacter(path[0]), ResultInvalidPathFormat);
+
+            *out = false;
+            R_SUCCEED();
+        }
+
+        // Process relative path
+        size_t relative_len = 0;
+        R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path));
+
+        // If we have a relative path, check that was allowed
+        if (relative_len > 0) {
+            R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
+
+            total_len += relative_len;
+
+            if (path[0] == NullTerminator) {
+                *out = true;
+                *out_len = total_len;
+                R_SUCCEED();
+            }
+        }
+
+        // Process windows path
+        size_t windows_len = 0;
+        bool normalized_win = false;
+        R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len),
+                              std::addressof(normalized_win), path, mount_name_len > 0));
+
+        // If the windows path wasn't normalized, we're not normalized
+        if (!normalized_win) {
+            R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
+
+            *out = false;
+            R_SUCCEED();
+        }
+
+        // If we had a windows path, check that was allowed
+        if (windows_len > 0) {
+            R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
+
+            total_len += windows_len;
+
+            // We can't have both a relative path and a windows path
+            R_UNLESS(relative_len == 0, ResultInvalidPathFormat);
+
+            // A path ending in a windows path isn't normalized
+            if (path[0] == NullTerminator) {
+                *out = false;
+                R_SUCCEED();
+            }
+
+            // Check that there are no windows directory separators in the path
+            for (size_t i = 0; path[i] != NullTerminator; ++i) {
+                if (path[i] == AlternateDirectorySeparator) {
+                    *out = false;
+                    R_SUCCEED();
+                }
+            }
+        }
+
+        // Check that parent directory replacement is not needed if backslashes are allowed
+        if (flags.IsBackslashAllowed() &&
+            PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) {
+            *out = false;
+            R_SUCCEED();
+        }
+
+        // Check that the backslash state is valid
+        bool is_backslash_contained = false;
+        R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path,
+                                    flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
+
+        // Check that backslashes are contained only if allowed
+        if (is_backslash_contained && !flags.IsBackslashAllowed()) {
+            *out = false;
+            R_SUCCEED();
+        }
+
+        // Check that the final result path is normalized
+        size_t normal_len = 0;
+        R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path,
+                                           flags.IsAllCharactersAllowed()));
+
+        // Add the normal length
+        total_len += normal_len;
+
+        // Set the output length
+        *out_len = total_len;
+        R_SUCCEED();
+    }
+
+    static Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
+                            const PathFlags& flags) {
+        // Use StringTraits names for remainder of scope
+        using namespace StringTraits;
+
+        // Prepare to iterate
+        const char* src = path;
+        size_t cur_pos = 0;
+        bool is_windows_path = false;
+
+        // Check if the path is empty
+        if (src[0] == NullTerminator) {
+            if (dst_size != 0) {
+                dst[0] = NullTerminator;
+            }
+
+            R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
+
+            R_SUCCEED();
+        }
+
+        // Handle a mount name
+        size_t mount_name_len = 0;
+        if (flags.IsMountNameAllowed()) {
+            R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos,
+                                 dst_size - cur_pos, src));
+
+            cur_pos += mount_name_len;
+        }
+
+        // Handle a drive-relative prefix
+        bool is_drive_relative = false;
+        if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) &&
+            !IsWindowsPath(src, false)) {
+            R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
+            R_UNLESS(!IsInvalidCharacter(src[0]), ResultInvalidPathFormat);
+
+            dst[cur_pos++] = Dot;
+            is_drive_relative = true;
+        }
+
+        size_t relative_len = 0;
+        if (flags.IsRelativePathAllowed()) {
+            R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
+
+            R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len),
+                                       dst + cur_pos, dst_size - cur_pos, src));
+
+            cur_pos += relative_len;
+
+            if (src[0] == NullTerminator) {
+                R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
+
+                dst[cur_pos] = NullTerminator;
+                R_SUCCEED();
+            }
+        }
+
+        // Handle a windows path
+        if (flags.IsWindowsPathAllowed()) {
+            const char* const orig = src;
+
+            R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
+
+            size_t windows_len = 0;
+            R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos,
+                                   dst_size - cur_pos, src, mount_name_len != 0));
+
+            cur_pos += windows_len;
+
+            if (src[0] == NullTerminator) {
+                /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */
+                R_UNLESS(cur_pos + 1 < dst_size, ResultTooLongPath);
+
+                dst[cur_pos + 0] = DirectorySeparator;
+                dst[cur_pos + 1] = NullTerminator;
+                R_SUCCEED();
+            }
+
+            if ((src - orig) > 0) {
+                is_windows_path = true;
+            }
+        }
+
+        // Check for invalid backslash
+        bool backslash_contained = false;
+        R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src,
+                                    flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
+
+        // Handle backslash replacement as necessary
+        if (backslash_contained && flags.IsWindowsPathAllowed()) {
+            // Create a temporary buffer holding a slash-replaced version of the path.
+            // NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path,
+            // despite having skipped some of it already.
+            const size_t replaced_src_len = path_len - (src - path);
+
+            char* replaced_src = nullptr;
+            SCOPE_EXIT({
+                if (replaced_src != nullptr) {
+                    if (std::is_constant_evaluated()) {
+                        delete[] replaced_src;
+                    } else {
+                        Deallocate(replaced_src, replaced_src_len);
+                    }
+                }
+            });
+
+            if (std::is_constant_evaluated()) {
+                replaced_src = new char[replaced_src_len];
+            } else {
+                replaced_src = static_cast<char*>(Allocate(replaced_src_len));
+            }
+
+            Strlcpy<char>(replaced_src, src, replaced_src_len);
+
+            Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator,
+                    DirectorySeparator);
+
+            size_t dummy;
+            R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src,
+                                            dst_size - cur_pos, is_windows_path, is_drive_relative,
+                                            flags.IsAllCharactersAllowed()));
+        } else {
+            // We can just do normalization
+            size_t dummy;
+            R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src,
+                                            dst_size - cur_pos, is_windows_path, is_drive_relative,
+                                            flags.IsAllCharactersAllowed()));
+        }
+
+        R_SUCCEED();
+    }
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fs_string_util.h b/src/core/file_sys/fs_string_util.h
new file mode 100755
index 000000000..874e09054
--- /dev/null
+++ b/src/core/file_sys/fs_string_util.h
@@ -0,0 +1,226 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/assert.h"
+
+namespace FileSys {
+
+template <typename T>
+constexpr int Strlen(const T* str) {
+    ASSERT(str != nullptr);
+
+    int length = 0;
+    while (*str++) {
+        ++length;
+    }
+
+    return length;
+}
+
+template <typename T>
+constexpr int Strnlen(const T* str, int count) {
+    ASSERT(str != nullptr);
+    ASSERT(count >= 0);
+
+    int length = 0;
+    while (count-- && *str++) {
+        ++length;
+    }
+
+    return length;
+}
+
+template <typename T>
+constexpr int Strncmp(const T* lhs, const T* rhs, int count) {
+    ASSERT(lhs != nullptr);
+    ASSERT(rhs != nullptr);
+    ASSERT(count >= 0);
+
+    if (count == 0) {
+        return 0;
+    }
+
+    T l, r;
+    do {
+        l = *(lhs++);
+        r = *(rhs++);
+    } while (l && (l == r) && (--count));
+
+    return l - r;
+}
+
+template <typename T>
+static constexpr int Strlcpy(T* dst, const T* src, int count) {
+    ASSERT(dst != nullptr);
+    ASSERT(src != nullptr);
+
+    const T* cur = src;
+    if (count > 0) {
+        while ((--count) && *cur) {
+            *(dst++) = *(cur++);
+        }
+        *dst = 0;
+    }
+
+    while (*cur) {
+        cur++;
+    }
+
+    return static_cast<int>(cur - src);
+}
+
+enum CharacterEncodingResult {
+    CharacterEncodingResult_Success = 0,
+    CharacterEncodingResult_InsufficientLength = 1,
+    CharacterEncodingResult_InvalidFormat = 2,
+};
+
+namespace impl {
+
+class CharacterEncodingHelper {
+public:
+    static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = {
+        -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+        1,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+        1,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+        1,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+        1,  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+        2,  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
+        3,  3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8,
+    };
+
+    static constexpr char GetUtf8NBytes(size_t i) {
+        return static_cast<char>(Utf8NBytesInnerTable[1 + i]);
+    }
+};
+
+} // namespace impl
+
+constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dst, const char* src) {
+    // Check pre-conditions
+    ASSERT(dst != nullptr);
+    ASSERT(src != nullptr);
+
+    // Perform the conversion
+    const auto* p = src;
+    switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[0]))) {
+    case 1:
+        *dst = static_cast<u32>(p[0]);
+        return CharacterEncodingResult_Success;
+    case 2:
+        if ((static_cast<u32>(p[0]) & 0x1E) != 0) {
+            if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
+                0) {
+                *dst = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
+                return CharacterEncodingResult_Success;
+            }
+        }
+        break;
+    case 3:
+        if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
+            const u32 c = (static_cast<u32>(p[0] & 0xF) << 12) |
+                          (static_cast<u32>(p[1] & 0x3F) << 6) |
+                          (static_cast<u32>(p[2] & 0x3F) << 0);
+            if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
+                *dst = c;
+                return CharacterEncodingResult_Success;
+            }
+        }
+        return CharacterEncodingResult_InvalidFormat;
+    case 4:
+        if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
+            const u32 c =
+                (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
+                (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
+            if (c >= 0x10000 && c < 0x110000) {
+                *dst = c;
+                return CharacterEncodingResult_Success;
+            }
+        }
+        return CharacterEncodingResult_InvalidFormat;
+    default:
+        break;
+    }
+
+    // We failed to convert
+    return CharacterEncodingResult_InvalidFormat;
+}
+
+constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char* dst,
+                                                                        const char** str) {
+    // Check pre-conditions
+    ASSERT(dst != nullptr);
+    ASSERT(str != nullptr);
+    ASSERT(*str != nullptr);
+
+    // Clear the output
+    dst[0] = 0;
+    dst[1] = 0;
+    dst[2] = 0;
+    dst[3] = 0;
+
+    // Perform the conversion
+    const auto* p = *str;
+    u32 c = static_cast<u32>(*p);
+    switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) {
+    case 1:
+        dst[0] = (*str)[0];
+        ++(*str);
+        break;
+    case 2:
+        if ((p[0] & 0x1E) != 0) {
+            if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
+                0) {
+                c = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
+                dst[0] = (*str)[0];
+                dst[1] = (*str)[1];
+                (*str) += 2;
+                break;
+            }
+        }
+        return CharacterEncodingResult_InvalidFormat;
+    case 3:
+        if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
+            c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) |
+                (static_cast<u32>(p[2] & 0x3F) << 0);
+            if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
+                dst[0] = (*str)[0];
+                dst[1] = (*str)[1];
+                dst[2] = (*str)[2];
+                (*str) += 3;
+                break;
+            }
+        }
+        return CharacterEncodingResult_InvalidFormat;
+    case 4:
+        if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
+            impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
+            c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
+                (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
+            if (c >= 0x10000 && c < 0x110000) {
+                dst[0] = (*str)[0];
+                dst[1] = (*str)[1];
+                dst[2] = (*str)[2];
+                dst[3] = (*str)[3];
+                (*str) += 4;
+                break;
+            }
+        }
+        return CharacterEncodingResult_InvalidFormat;
+    default:
+        return CharacterEncodingResult_InvalidFormat;
+    }
+
+    return CharacterEncodingResult_Success;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index de4dc5ed7..ab51a7778 100755
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -8,8 +8,8 @@
 #include "common/assert.h"
 #include "core/file_sys/fsmitm_romfsbuild.h"
 #include "core/file_sys/ips_layer.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index b69bee23a..8984e87ab 100755
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -7,7 +7,7 @@
 #include <memory>
 #include <string>
 #include "common/common_types.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h
index 416dd57b8..37336c9ae 100755
--- a/src/core/file_sys/fssystem/fs_i_storage.h
+++ b/src/core/file_sys/fssystem/fs_i_storage.h
@@ -5,7 +5,7 @@
 
 #include "common/overflow.h"
 #include "core/file_sys/errors.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
index f25c95472..bc1cddbb0 100755
--- a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
@@ -4,7 +4,7 @@
 #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h"
 #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h"
 #include "core/file_sys/fssystem/fssystem_nca_header.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
index 339e49697..5abd93d33 100755
--- a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
@@ -9,7 +9,7 @@
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/fssystem/fs_i_storage.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
index 46850cd48..3a5e21d1a 100755
--- a/src/core/file_sys/fssystem/fssystem_bucket_tree.h
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h
@@ -10,7 +10,7 @@
 #include "common/common_types.h"
 #include "common/literals.h"
 
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/result.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
index 33d93938e..74c98630e 100755
--- a/src/core/file_sys/fssystem/fssystem_compressed_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h
@@ -10,7 +10,7 @@
 #include "core/file_sys/fssystem/fssystem_bucket_tree.h"
 #include "core/file_sys/fssystem/fssystem_compression_common.h"
 #include "core/file_sys/fssystem/fssystem_pooled_buffer.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
index 4a75b5308..39bb7b808 100755
--- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
index 5cf697efe..bd129db47 100755
--- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
@@ -8,7 +8,7 @@
 #include "core/file_sys/fssystem/fs_types.h"
 #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h"
 #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
index 18df400af..41d3960b8 100755
--- a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
@@ -7,7 +7,7 @@
 
 #include "core/file_sys/errors.h"
 #include "core/file_sys/fssystem/fs_i_storage.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
index 7854335bf..d4b95fd27 100755
--- a/src/core/file_sys/fssystem/fssystem_indirect_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
@@ -7,8 +7,8 @@
 #include "core/file_sys/fssystem/fs_i_storage.h"
 #include "core/file_sys/fssystem/fssystem_bucket_tree.h"
 #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
index 5f8512b2a..240d1e388 100755
--- a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
@@ -5,7 +5,7 @@
 
 #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h"
 #include "core/file_sys/fssystem/fssystem_nca_header.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
index 0f5432203..ab5a7984e 100755
--- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
@@ -14,8 +14,8 @@
 #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
 #include "core/file_sys/fssystem/fssystem_sparse_storage.h"
 #include "core/file_sys/fssystem/fssystem_switch_storage.h"
-#include "core/file_sys/vfs_offset.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
index 5771a21fc..5bc838de6 100755
--- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
@@ -5,7 +5,7 @@
 
 #include "core/file_sys/fssystem/fssystem_compression_common.h"
 #include "core/file_sys/fssystem/fssystem_nca_header.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
index a3714ab37..08924e2a6 100755
--- a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
+++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp
@@ -3,7 +3,7 @@
 
 #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h"
 #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 69abc37b5..1d6148e43 100755
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -12,7 +12,7 @@
 #include "common/logging/log.h"
 #include "common/swap.h"
 #include "core/file_sys/ips_layer.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
index b78a55084..a645b2dd7 100755
--- a/src/core/file_sys/ips_layer.h
+++ b/src/core/file_sys/ips_layer.h
@@ -8,7 +8,7 @@
 #include <vector>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index 6e7f22701..992c486d4 100755
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -5,7 +5,7 @@
 
 #include "common/string_util.h"
 #include "core/file_sys/kernel_executable.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/loader/loader.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 4e803c6b7..0132e8071 100755
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -10,7 +10,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index ef5b8f7c3..8d00bf9df 100755
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -6,7 +6,7 @@
 #include "common/logging/log.h"
 #include "common/swap.h"
 #include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 2c9e5c956..e08ab1ccb 100755
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -8,7 +8,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys {
 class CNMT;
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index ee591a131..2d8b910dd 100755
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -9,7 +9,7 @@
 
 #include "common/logging/log.h"
 #include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/loader/loader.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 2ba2feee1..d529b8d59 100755
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -9,7 +9,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index ba7d85e6f..467f94029 100755
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -21,9 +21,9 @@
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs.h"
-#include "core/file_sys/vfs_cached.h"
-#include "core/file_sys/vfs_layered.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_cached.h"
+#include "core/file_sys/vfs/vfs_layered.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/ns/language.h"
 #include "core/hle/service/set/settings_server.h"
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 8d682d42f..e1c35af3b 100755
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -9,7 +9,7 @@
 #include <string>
 #include "common/common_types.h"
 #include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/memory/dmnt_cheat_types.h"
 
 namespace Core {
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 382fb418f..37c79f264 100755
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -7,7 +7,7 @@
 #include "common/logging/log.h"
 #include "common/scope_exit.h"
 #include "core/file_sys/program_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/loader/loader.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 70d87ff99..c5fbbfd5a 100755
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -10,7 +10,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index e98cc53b5..706f03aa6 100755
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -17,7 +17,7 @@
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/submission_package.h"
-#include "core/file_sys/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_concat.h"
 #include "core/loader/loader.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index bac1a042e..81c527ea0 100755
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -11,7 +11,7 @@
 #include <boost/container/flat_map.hpp>
 #include "common/common_types.h"
 #include "core/crypto/key_manager.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 class CNMT;
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index cc3d540f5..437836424 100755
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -9,11 +9,11 @@
 #include "common/swap.h"
 #include "core/file_sys/fsmitm_romfsbuild.h"
 #include "core/file_sys/romfs.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_cached.h"
-#include "core/file_sys/vfs_concat.h"
-#include "core/file_sys/vfs_offset.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_cached.h"
+#include "core/file_sys/vfs/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 namespace {
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 1835836db..ccfefee51 100755
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index d01025fb5..e59bd3ab0 100755
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -6,7 +6,7 @@
 #include <memory>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/hle/result.h"
 
 namespace Loader {
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 668a82f23..aa2f7e8f4 100755
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -8,7 +8,7 @@
 #include "common/uuid.h"
 #include "core/core.h"
 #include "core/file_sys/savedata_factory.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 2f1047d43..2134eb405 100755
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -7,7 +7,7 @@
 #include <string>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/result.h"
 
 namespace Core {
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index b044817bc..75ed7585a 100755
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -4,7 +4,7 @@
 #include <memory>
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/sdmc_factory.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/file_sys/xts_archive.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 8dc65fa13..d1c170670 100755
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -4,7 +4,7 @@
 #pragma once
 
 #include <memory>
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/hle/result.h"
 
 namespace FileSys {
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index e6031f924..d10980fa5 100755
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -9,7 +9,7 @@
 #include <vector>
 #include "common/common_types.h"
 #include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Core::Crypto {
 class KeyManager;
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
index a7fefc40e..b67c21218 100755
--- a/src/core/file_sys/system_archive/mii_model.cpp
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "core/file_sys/system_archive/mii_model.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h
index ee12da595..9ff460290 100755
--- a/src/core/file_sys/system_archive/mii_model.h
+++ b/src/core/file_sys/system_archive/mii_model.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
index cbcd19f13..0f3af1684 100755
--- a/src/core/file_sys/system_archive/ng_word.cpp
+++ b/src/core/file_sys/system_archive/ng_word.cpp
@@ -4,7 +4,7 @@
 #include <fmt/format.h>
 #include "common/common_types.h"
 #include "core/file_sys/system_archive/ng_word.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h
index e00fedcfe..821b5c001 100755
--- a/src/core/file_sys/system_archive/ng_word.h
+++ b/src/core/file_sys/system_archive/ng_word.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/shared_font.cpp b/src/core/file_sys/system_archive/shared_font.cpp
index 0b00f1e88..e378adcb3 100755
--- a/src/core/file_sys/system_archive/shared_font.cpp
+++ b/src/core/file_sys/system_archive/shared_font.cpp
@@ -8,7 +8,7 @@
 #include "core/file_sys/system_archive/data/font_nintendo_extended.h"
 #include "core/file_sys/system_archive/data/font_standard.h"
 #include "core/file_sys/system_archive/shared_font.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/hle/service/ns/iplatform_service_manager.h"
 
 namespace FileSys::SystemArchive {
diff --git a/src/core/file_sys/system_archive/shared_font.h b/src/core/file_sys/system_archive/shared_font.h
index 42acc5613..27d58ae3a 100755
--- a/src/core/file_sys/system_archive/shared_font.h
+++ b/src/core/file_sys/system_archive/shared_font.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h
index 149f8974b..4de1c4a48 100755
--- a/src/core/file_sys/system_archive/system_archive.h
+++ b/src/core/file_sys/system_archive/system_archive.h
@@ -4,7 +4,7 @@
 #pragma once
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index 1152b2731..964169fd8 100755
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -3,7 +3,7 @@
 
 #include "common/logging/log.h"
 #include "core/file_sys/system_archive/system_version.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/hle/api_version.h"
 
 namespace FileSys::SystemArchive {
diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h
index f3247755c..85f778c74 100755
--- a/src/core/file_sys/system_archive/system_version.h
+++ b/src/core/file_sys/system_archive/system_version.h
@@ -4,7 +4,7 @@
 #pragma once
 
 #include <string>
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index d4fb1d983..2069032e2 100755
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -5,7 +5,7 @@
 
 #include "common/swap.h"
 #include "core/file_sys/system_archive/time_zone_binary.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 #include "nx_tzdb.h"
 
diff --git a/src/core/file_sys/system_archive/time_zone_binary.h b/src/core/file_sys/system_archive/time_zone_binary.h
index 6a8a73255..6ca1fdf8d 100755
--- a/src/core/file_sys/system_archive/time_zone_binary.h
+++ b/src/core/file_sys/system_archive/time_zone_binary.h
@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys::SystemArchive {
 
diff --git a/src/core/file_sys/vfs/vfs.cpp b/src/core/file_sys/vfs/vfs.cpp
new file mode 100755
index 000000000..a04292760
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.cpp
@@ -0,0 +1,551 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <numeric>
+#include <string>
+#include "common/fs/path_util.h"
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
+
+VfsFilesystem::~VfsFilesystem() = default;
+
+std::string VfsFilesystem::GetName() const {
+    return root->GetName();
+}
+
+bool VfsFilesystem::IsReadable() const {
+    return root->IsReadable();
+}
+
+bool VfsFilesystem::IsWritable() const {
+    return root->IsWritable();
+}
+
+VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
+    const auto path = Common::FS::SanitizePath(path_);
+    if (root->GetFileRelative(path) != nullptr)
+        return VfsEntryType::File;
+    if (root->GetDirectoryRelative(path) != nullptr)
+        return VfsEntryType::Directory;
+
+    return VfsEntryType::None;
+}
+
+VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
+    const auto path = Common::FS::SanitizePath(path_);
+    return root->GetFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
+    const auto path = Common::FS::SanitizePath(path_);
+    return root->CreateFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = Common::FS::SanitizePath(old_path_);
+    const auto new_path = Common::FS::SanitizePath(new_path_);
+
+    // VfsDirectory impls are only required to implement copy across the current directory.
+    if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
+        if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
+            return nullptr;
+        return OpenFile(new_path, OpenMode::ReadWrite);
+    }
+
+    // Do it using RawCopy. Non-default impls are encouraged to optimize this.
+    const auto old_file = OpenFile(old_path, OpenMode::Read);
+    if (old_file == nullptr)
+        return nullptr;
+    auto new_file = OpenFile(new_path, OpenMode::Read);
+    if (new_file != nullptr)
+        return nullptr;
+    new_file = CreateFile(new_path, OpenMode::Write);
+    if (new_file == nullptr)
+        return nullptr;
+    if (!VfsRawCopy(old_file, new_file))
+        return nullptr;
+    return new_file;
+}
+
+VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
+    const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+    const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
+
+    // Again, non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyFile(sanitized_old_path, sanitized_new_path);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteFile(sanitized_old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteFile(std::string_view path_) {
+    const auto path = Common::FS::SanitizePath(path_);
+    auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteFile(Common::FS::GetFilename(path));
+}
+
+VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
+    const auto path = Common::FS::SanitizePath(path_);
+    return root->GetDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
+    const auto path = Common::FS::SanitizePath(path_);
+    return root->CreateDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = Common::FS::SanitizePath(old_path_);
+    const auto new_path = Common::FS::SanitizePath(new_path_);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto old_dir = OpenDirectory(old_path, OpenMode::Read);
+    if (old_dir == nullptr)
+        return nullptr;
+    auto new_dir = OpenDirectory(new_path, OpenMode::Read);
+    if (new_dir != nullptr)
+        return nullptr;
+    new_dir = CreateDirectory(new_path, OpenMode::Write);
+    if (new_dir == nullptr)
+        return nullptr;
+
+    for (const auto& file : old_dir->GetFiles()) {
+        const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    for (const auto& dir : old_dir->GetSubdirectories()) {
+        const auto x =
+            CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName());
+        if (x == nullptr)
+            return nullptr;
+    }
+
+    return new_dir;
+}
+
+VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
+    const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+    const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
+
+    // Non-default impls are highly encouraged to provide a more optimized version of this.
+    auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
+    if (out == nullptr)
+        return nullptr;
+    if (DeleteDirectory(sanitized_old_path))
+        return out;
+    return nullptr;
+}
+
+bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
+    const auto path = Common::FS::SanitizePath(path_);
+    auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write);
+    if (parent == nullptr)
+        return false;
+    return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
+}
+
+VfsFile::~VfsFile() = default;
+
+std::string VfsFile::GetExtension() const {
+    return std::string(Common::FS::GetExtensionFromFilename(GetName()));
+}
+
+VfsDirectory::~VfsDirectory() = default;
+
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+    u8 out{};
+    const std::size_t size = Read(&out, sizeof(u8), offset);
+    if (size == 1) {
+        return out;
+    }
+
+    return std::nullopt;
+}
+
+std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
+    std::vector<u8> out(size);
+    std::size_t read_size = Read(out.data(), size, offset);
+    out.resize(read_size);
+    return out;
+}
+
+std::vector<u8> VfsFile::ReadAllBytes() const {
+    return ReadBytes(GetSize());
+}
+
+bool VfsFile::WriteByte(u8 data, std::size_t offset) {
+    return Write(&data, 1, offset) == 1;
+}
+
+std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) {
+    return Write(data.data(), data.size(), offset);
+}
+
+std::string VfsFile::GetFullPath() const {
+    if (GetContainingDirectory() == nullptr)
+        return '/' + GetName();
+
+    return GetContainingDirectory()->GetFullPath() + '/' + GetName();
+}
+
+VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
+    auto vec = Common::FS::SplitPathComponents(path);
+    if (vec.empty()) {
+        return nullptr;
+    }
+
+    if (vec.size() == 1) {
+        return GetFile(vec[0]);
+    }
+
+    auto dir = GetSubdirectory(vec[0]);
+    for (std::size_t component = 1; component < vec.size() - 1; ++component) {
+        if (dir == nullptr) {
+            return nullptr;
+        }
+
+        dir = dir->GetSubdirectory(vec[component]);
+    }
+
+    if (dir == nullptr) {
+        return nullptr;
+    }
+
+    return dir->GetFile(vec.back());
+}
+
+VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
+    if (IsRoot()) {
+        return GetFileRelative(path);
+    }
+
+    return GetParentDirectory()->GetFileAbsolute(path);
+}
+
+VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
+    auto vec = Common::FS::SplitPathComponents(path);
+    if (vec.empty()) {
+        // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
+        // because of const-ness
+        return nullptr;
+    }
+
+    auto dir = GetSubdirectory(vec[0]);
+    for (std::size_t component = 1; component < vec.size(); ++component) {
+        if (dir == nullptr) {
+            return nullptr;
+        }
+
+        dir = dir->GetSubdirectory(vec[component]);
+    }
+
+    return dir;
+}
+
+VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
+    if (IsRoot()) {
+        return GetDirectoryRelative(path);
+    }
+
+    return GetParentDirectory()->GetDirectoryAbsolute(path);
+}
+
+VirtualFile VfsDirectory::GetFile(std::string_view name) const {
+    const auto& files = GetFiles();
+    const auto iter = std::find_if(files.begin(), files.end(),
+                                   [&name](const auto& file1) { return name == file1->GetName(); });
+    return iter == files.end() ? nullptr : *iter;
+}
+
+FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const {
+    return {};
+}
+
+VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
+    const auto& subs = GetSubdirectories();
+    const auto iter = std::find_if(subs.begin(), subs.end(),
+                                   [&name](const auto& file1) { return name == file1->GetName(); });
+    return iter == subs.end() ? nullptr : *iter;
+}
+
+bool VfsDirectory::IsRoot() const {
+    return GetParentDirectory() == nullptr;
+}
+
+std::size_t VfsDirectory::GetSize() const {
+    const auto& files = GetFiles();
+    const auto sum_sizes = [](const auto& range) {
+        return std::accumulate(range.begin(), range.end(), 0ULL,
+                               [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
+    };
+
+    const auto file_total = sum_sizes(files);
+    const auto& sub_dir = GetSubdirectories();
+    const auto subdir_total = sum_sizes(sub_dir);
+
+    return file_total + subdir_total;
+}
+
+VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
+    auto vec = Common::FS::SplitPathComponents(path);
+    if (vec.empty()) {
+        return nullptr;
+    }
+
+    if (vec.size() == 1) {
+        return CreateFile(vec[0]);
+    }
+
+    auto dir = GetSubdirectory(vec[0]);
+    if (dir == nullptr) {
+        dir = CreateSubdirectory(vec[0]);
+        if (dir == nullptr) {
+            return nullptr;
+        }
+    }
+
+    return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
+}
+
+VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
+    if (IsRoot()) {
+        return CreateFileRelative(path);
+    }
+
+    return GetParentDirectory()->CreateFileAbsolute(path);
+}
+
+VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
+    auto vec = Common::FS::SplitPathComponents(path);
+    if (vec.empty()) {
+        return nullptr;
+    }
+
+    if (vec.size() == 1) {
+        return CreateSubdirectory(vec[0]);
+    }
+
+    auto dir = GetSubdirectory(vec[0]);
+    if (dir == nullptr) {
+        dir = CreateSubdirectory(vec[0]);
+        if (dir == nullptr) {
+            return nullptr;
+        }
+    }
+
+    return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
+}
+
+VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+    if (IsRoot()) {
+        return CreateDirectoryRelative(path);
+    }
+
+    return GetParentDirectory()->CreateDirectoryAbsolute(path);
+}
+
+bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+    auto dir = GetSubdirectory(name);
+    if (dir == nullptr) {
+        return false;
+    }
+
+    bool success = true;
+    for (const auto& file : dir->GetFiles()) {
+        if (!DeleteFile(file->GetName())) {
+            success = false;
+        }
+    }
+
+    for (const auto& sdir : dir->GetSubdirectories()) {
+        if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
+            success = false;
+        }
+    }
+
+    return success;
+}
+
+bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+    auto dir = GetSubdirectory(name);
+    if (dir == nullptr) {
+        return false;
+    }
+
+    bool success = true;
+    for (const auto& file : dir->GetFiles()) {
+        if (!dir->DeleteFile(file->GetName())) {
+            success = false;
+        }
+    }
+
+    for (const auto& sdir : dir->GetSubdirectories()) {
+        if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
+            success = false;
+        }
+    }
+
+    return success;
+}
+
+bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
+    const auto f1 = GetFile(src);
+    auto f2 = CreateFile(dest);
+    if (f1 == nullptr || f2 == nullptr) {
+        return false;
+    }
+
+    if (!f2->Resize(f1->GetSize())) {
+        DeleteFile(dest);
+        return false;
+    }
+
+    return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
+}
+
+std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
+    std::map<std::string, VfsEntryType, std::less<>> out;
+    for (const auto& dir : GetSubdirectories())
+        out.emplace(dir->GetName(), VfsEntryType::Directory);
+    for (const auto& file : GetFiles())
+        out.emplace(file->GetName(), VfsEntryType::File);
+    return out;
+}
+
+std::string VfsDirectory::GetFullPath() const {
+    if (IsRoot())
+        return GetName();
+
+    return GetParentDirectory()->GetFullPath() + '/' + GetName();
+}
+
+bool ReadOnlyVfsDirectory::IsWritable() const {
+    return false;
+}
+
+bool ReadOnlyVfsDirectory::IsReadable() const {
+    return true;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
+    return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
+    return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
+    return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
+    return nullptr;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+    return nullptr;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+    return nullptr;
+}
+
+bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) {
+    return false;
+}
+
+bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+    return false;
+}
+
+bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+    return false;
+}
+
+bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) {
+    return false;
+}
+
+bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
+    return false;
+}
+
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) {
+    if (file1->GetSize() != file2->GetSize())
+        return false;
+
+    std::vector<u8> f1_v(block_size);
+    std::vector<u8> f2_v(block_size);
+    for (std::size_t i = 0; i < file1->GetSize(); i += block_size) {
+        auto f1_vs = file1->Read(f1_v.data(), block_size, i);
+        auto f2_vs = file2->Read(f2_v.data(), block_size, i);
+
+        if (f1_vs != f2_vs)
+            return false;
+        auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end());
+        if (iters.first != f1_v.end() && iters.second != f2_v.end())
+            return false;
+    }
+
+    return true;
+}
+
+bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
+    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+        return false;
+    if (!dest->Resize(src->GetSize()))
+        return false;
+
+    std::vector<u8> temp(std::min(block_size, src->GetSize()));
+    for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
+        const auto read = std::min(block_size, src->GetSize() - i);
+
+        if (src->Read(temp.data(), read, i) != read) {
+            return false;
+        }
+
+        if (dest->Write(temp.data(), read, i) != read) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
+    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+        return false;
+
+    for (const auto& file : src->GetFiles()) {
+        const auto out = dest->CreateFile(file->GetName());
+        if (!VfsRawCopy(file, out, block_size))
+            return false;
+    }
+
+    for (const auto& dir : src->GetSubdirectories()) {
+        const auto out = dest->CreateSubdirectory(dir->GetName());
+        if (!VfsRawCopyD(dir, out, block_size))
+            return false;
+    }
+
+    return true;
+}
+
+VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
+    const auto res = rel->GetDirectoryRelative(path);
+    if (res == nullptr)
+        return rel->CreateDirectoryRelative(path);
+    return res;
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs.h b/src/core/file_sys/vfs/vfs.h
new file mode 100755
index 000000000..f846a9669
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.h
@@ -0,0 +1,326 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/vfs/vfs_types.h"
+
+namespace FileSys {
+
+// An enumeration representing what can be at the end of a path in a VfsFilesystem
+enum class VfsEntryType {
+    None,
+    File,
+    Directory,
+};
+
+// A class representing an abstract filesystem. A default implementation given the root VirtualDir
+// is provided for convenience, but if the Vfs implementation has any additional state or
+// functionality, they will need to override.
+class VfsFilesystem {
+public:
+    YUZU_NON_COPYABLE(VfsFilesystem);
+    YUZU_NON_MOVEABLE(VfsFilesystem);
+
+    explicit VfsFilesystem(VirtualDir root);
+    virtual ~VfsFilesystem();
+
+    // Gets the friendly name for the filesystem.
+    virtual std::string GetName() const;
+
+    // Return whether or not the user has read permissions on this filesystem.
+    virtual bool IsReadable() const;
+    // Return whether or not the user has write permission on this filesystem.
+    virtual bool IsWritable() const;
+
+    // Determine if the entry at path is non-existent, a file, or a directory.
+    virtual VfsEntryType GetEntryType(std::string_view path) const;
+
+    // Opens the file with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualFile OpenFile(std::string_view path, OpenMode perms);
+    // Creates a new, empty file at path
+    virtual VirtualFile CreateFile(std::string_view path, OpenMode perms);
+    // Copies the file from old_path to new_path, returning the new file on success and nullptr on
+    // failure.
+    virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
+    // Moves the file from old_path to new_path, returning the moved file on success and nullptr on
+    // failure.
+    virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
+    // Deletes the file with path relative to root, returning true on success.
+    virtual bool DeleteFile(std::string_view path);
+
+    // Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
+    virtual VirtualDir OpenDirectory(std::string_view path, OpenMode perms);
+    // Creates a new, empty directory at path
+    virtual VirtualDir CreateDirectory(std::string_view path, OpenMode perms);
+    // Copies the directory from old_path to new_path, returning the new directory on success and
+    // nullptr on failure.
+    virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
+    // Moves the directory from old_path to new_path, returning the moved directory on success and
+    // nullptr on failure.
+    virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
+    // Deletes the directory with path relative to root, returning true on success.
+    virtual bool DeleteDirectory(std::string_view path);
+
+protected:
+    // Root directory in default implementation.
+    VirtualDir root;
+};
+
+// A class representing a file in an abstract filesystem.
+class VfsFile {
+public:
+    YUZU_NON_COPYABLE(VfsFile);
+    YUZU_NON_MOVEABLE(VfsFile);
+
+    VfsFile() = default;
+    virtual ~VfsFile();
+
+    // Retrieves the file name.
+    virtual std::string GetName() const = 0;
+    // Retrieves the extension of the file name.
+    virtual std::string GetExtension() const;
+    // Retrieves the size of the file.
+    virtual std::size_t GetSize() const = 0;
+    // Resizes the file to new_size. Returns whether or not the operation was successful.
+    virtual bool Resize(std::size_t new_size) = 0;
+    // Gets a pointer to the directory containing this file, returning nullptr if there is none.
+    virtual VirtualDir GetContainingDirectory() const = 0;
+
+    // Returns whether or not the file can be written to.
+    virtual bool IsWritable() const = 0;
+    // Returns whether or not the file can be read from.
+    virtual bool IsReadable() const = 0;
+
+    // The primary method of reading from the file. Reads length bytes into data starting at offset
+    // into file. Returns number of bytes successfully read.
+    virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
+    // The primary method of writing to the file. Writes length bytes from data starting at offset
+    // into file. Returns number of bytes successfully written.
+    virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
+
+    // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+    virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
+    // Reads size bytes starting at offset in file into a vector.
+    virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
+    // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
+    // 0)'
+    virtual std::vector<u8> ReadAllBytes() const;
+
+    // Reads an array of type T, size number_elements starting at offset.
+    // Returns the number of bytes (sizeof(T)*number_elements) read successfully.
+    template <typename T>
+    std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+
+        return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
+    }
+
+    // Reads size bytes into the memory starting at data starting at offset into the file.
+    // Returns the number of bytes read successfully.
+    template <typename T>
+    std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+        return Read(reinterpret_cast<u8*>(data), size, offset);
+    }
+
+    // Reads one object of type T starting at offset in file.
+    // Returns the number of bytes read successfully (sizeof(T)).
+    template <typename T>
+    std::size_t ReadObject(T* data, std::size_t offset = 0) const {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+        return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
+    }
+
+    // Writes exactly one byte to offset in file and returns whether or not the byte was written
+    // successfully.
+    virtual bool WriteByte(u8 data, std::size_t offset = 0);
+    // Writes a vector of bytes to offset in file and returns the number of bytes successfully
+    // written.
+    virtual std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset = 0);
+
+    // Writes an array of type T, size number_elements to offset in file.
+    // Returns the number of bytes (sizeof(T)*number_elements) written successfully.
+    template <typename T>
+    std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+        return Write(reinterpret_cast<const u8*>(data), number_elements * sizeof(T), offset);
+    }
+
+    // Writes size bytes starting at memory location data to offset in file.
+    // Returns the number of bytes written successfully.
+    template <typename T>
+    std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+        return Write(reinterpret_cast<const u8*>(data), size, offset);
+    }
+
+    // Writes one object of type T to offset in file.
+    // Returns the number of bytes written successfully (sizeof(T)).
+    template <typename T>
+    std::size_t WriteObject(const T& data, std::size_t offset = 0) {
+        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+        return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
+    }
+
+    // Renames the file to name. Returns whether or not the operation was successful.
+    virtual bool Rename(std::string_view name) = 0;
+
+    // Returns the full path of this file as a string, recursively
+    virtual std::string GetFullPath() const;
+};
+
+// A class representing a directory in an abstract filesystem.
+class VfsDirectory {
+public:
+    YUZU_NON_COPYABLE(VfsDirectory);
+    YUZU_NON_MOVEABLE(VfsDirectory);
+
+    VfsDirectory() = default;
+    virtual ~VfsDirectory();
+
+    // Retrieves the file located at path as if the current directory was root. Returns nullptr if
+    // not found.
+    virtual VirtualFile GetFileRelative(std::string_view path) const;
+    // Calls GetFileRelative(path) on the root of the current directory.
+    virtual VirtualFile GetFileAbsolute(std::string_view path) const;
+
+    // Retrieves the directory located at path as if the current directory was root. Returns nullptr
+    // if not found.
+    virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
+    // Calls GetDirectoryRelative(path) on the root of the current directory.
+    virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
+
+    // Returns a vector containing all of the files in this directory.
+    virtual std::vector<VirtualFile> GetFiles() const = 0;
+    // Returns the file with filename matching name. Returns nullptr if directory doesn't have a
+    // file with name.
+    virtual VirtualFile GetFile(std::string_view name) const;
+
+    // Returns a struct containing the file's timestamp.
+    virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const;
+
+    // Returns a vector containing all of the subdirectories in this directory.
+    virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
+    // Returns the directory with name matching name. Returns nullptr if directory doesn't have a
+    // directory with name.
+    virtual VirtualDir GetSubdirectory(std::string_view name) const;
+
+    // Returns whether or not the directory can be written to.
+    virtual bool IsWritable() const = 0;
+    // Returns whether of not the directory can be read from.
+    virtual bool IsReadable() const = 0;
+
+    // Returns whether or not the directory is the root of the current file tree.
+    virtual bool IsRoot() const;
+
+    // Returns the name of the directory.
+    virtual std::string GetName() const = 0;
+    // Returns the total size of all files and subdirectories in this directory.
+    virtual std::size_t GetSize() const;
+    // Returns the parent directory of this directory. Returns nullptr if this directory is root or
+    // has no parent.
+    virtual VirtualDir GetParentDirectory() const = 0;
+
+    // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
+    // if the operation failed.
+    virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
+    // Creates a new file with name name. Returns a pointer to the new file or nullptr if the
+    // operation failed.
+    virtual VirtualFile CreateFile(std::string_view name) = 0;
+
+    // Creates a new file at the path relative to this directory. Also creates directories if
+    // they do not exist and is supported by this implementation. Returns nullptr on any failure.
+    virtual VirtualFile CreateFileRelative(std::string_view path);
+
+    // Creates a new file at the path relative to root of this directory. Also creates directories
+    // if they do not exist and is supported by this implementation. Returns nullptr on any failure.
+    virtual VirtualFile CreateFileAbsolute(std::string_view path);
+
+    // Creates a new directory at the path relative to this directory. Also creates directories if
+    // they do not exist and is supported by this implementation. Returns nullptr on any failure.
+    virtual VirtualDir CreateDirectoryRelative(std::string_view path);
+
+    // Creates a new directory at the path relative to root of this directory. Also creates
+    // directories if they do not exist and is supported by this implementation. Returns nullptr on
+    // any failure.
+    virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
+
+    // Deletes the subdirectory with the given name and returns true on success.
+    virtual bool DeleteSubdirectory(std::string_view name) = 0;
+
+    // Deletes all subdirectories and files within the provided directory and then deletes
+    // the directory itself. Returns true on success.
+    virtual bool DeleteSubdirectoryRecursive(std::string_view name);
+
+    // Deletes all subdirectories and files within the provided directory.
+    // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory.
+    virtual bool CleanSubdirectoryRecursive(std::string_view name);
+
+    // Returns whether or not the file with name name was deleted successfully.
+    virtual bool DeleteFile(std::string_view name) = 0;
+
+    // Returns whether or not this directory was renamed to name.
+    virtual bool Rename(std::string_view name) = 0;
+
+    // Returns whether or not the file with name src was successfully copied to a new file with name
+    // dest.
+    virtual bool Copy(std::string_view src, std::string_view dest);
+
+    // Gets all of the entries directly in the directory (files and dirs), returning a map between
+    // item name -> type.
+    virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
+
+    // Returns the full path of this directory as a string, recursively
+    virtual std::string GetFullPath() const;
+};
+
+// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
+// if writable. This is to avoid redundant empty methods everywhere.
+class ReadOnlyVfsDirectory : public VfsDirectory {
+public:
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    VirtualDir CreateSubdirectory(std::string_view name) override;
+    VirtualFile CreateFile(std::string_view name) override;
+    VirtualFile CreateFileAbsolute(std::string_view path) override;
+    VirtualFile CreateFileRelative(std::string_view path) override;
+    VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
+    VirtualDir CreateDirectoryRelative(std::string_view path) override;
+    bool DeleteSubdirectory(std::string_view name) override;
+    bool DeleteSubdirectoryRecursive(std::string_view name) override;
+    bool CleanSubdirectoryRecursive(std::string_view name) override;
+    bool DeleteFile(std::string_view name) override;
+    bool Rename(std::string_view name) override;
+};
+
+// Compare the two files, byte-for-byte, in increments specified by block_size
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
+                std::size_t block_size = 0x1000);
+
+// A method that copies the raw data between two different implementations of VirtualFile. If you
+// are using the same implementation, it is probably better to use the Copy method in the parent
+// directory of src/dest.
+bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
+
+// A method that performs a similar function to VfsRawCopy above, but instead copies entire
+// directories. It suffers the same performance penalties as above and an implementation-specific
+// Copy should always be preferred.
+bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
+
+// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
+// it attempts to create it and returns the new dir or nullptr on failure.
+VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_cached.cpp b/src/core/file_sys/vfs/vfs_cached.cpp
new file mode 100755
index 000000000..01cd0f1e0
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_cached.cpp
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/vfs/vfs_cached.h"
+#include "core/file_sys/vfs/vfs_types.h"
+
+namespace FileSys {
+
+CachedVfsDirectory::CachedVfsDirectory(VirtualDir&& source_dir)
+    : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) {
+    for (auto& dir : source_dir->GetSubdirectories()) {
+        dirs.emplace(dir->GetName(), std::make_shared<CachedVfsDirectory>(std::move(dir)));
+    }
+    for (auto& file : source_dir->GetFiles()) {
+        files.emplace(file->GetName(), std::move(file));
+    }
+}
+
+CachedVfsDirectory::~CachedVfsDirectory() = default;
+
+VirtualFile CachedVfsDirectory::GetFile(std::string_view file_name) const {
+    auto it = files.find(file_name);
+    if (it != files.end()) {
+        return it->second;
+    }
+
+    return nullptr;
+}
+
+VirtualDir CachedVfsDirectory::GetSubdirectory(std::string_view dir_name) const {
+    auto it = dirs.find(dir_name);
+    if (it != dirs.end()) {
+        return it->second;
+    }
+
+    return nullptr;
+}
+
+std::vector<VirtualFile> CachedVfsDirectory::GetFiles() const {
+    std::vector<VirtualFile> out;
+    for (auto& [file_name, file] : files) {
+        out.push_back(file);
+    }
+    return out;
+}
+
+std::vector<VirtualDir> CachedVfsDirectory::GetSubdirectories() const {
+    std::vector<VirtualDir> out;
+    for (auto& [dir_name, dir] : dirs) {
+        out.push_back(dir);
+    }
+    return out;
+}
+
+std::string CachedVfsDirectory::GetName() const {
+    return name;
+}
+
+VirtualDir CachedVfsDirectory::GetParentDirectory() const {
+    return parent;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_cached.h b/src/core/file_sys/vfs/vfs_cached.h
new file mode 100755
index 000000000..47dff7224
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_cached.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string_view>
+#include <vector>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+class CachedVfsDirectory : public ReadOnlyVfsDirectory {
+public:
+    CachedVfsDirectory(VirtualDir&& source_directory);
+
+    ~CachedVfsDirectory() override;
+    VirtualFile GetFile(std::string_view file_name) const override;
+    VirtualDir GetSubdirectory(std::string_view dir_name) const override;
+    std::vector<VirtualFile> GetFiles() const override;
+    std::vector<VirtualDir> GetSubdirectories() const override;
+    std::string GetName() const override;
+    VirtualDir GetParentDirectory() const override;
+
+private:
+    std::string name;
+    VirtualDir parent;
+    std::map<std::string, VirtualDir, std::less<>> dirs;
+    std::map<std::string, VirtualFile, std::less<>> files;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_concat.cpp b/src/core/file_sys/vfs/vfs_concat.cpp
new file mode 100755
index 000000000..b5cc9a9e9
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_concat.cpp
@@ -0,0 +1,192 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+
+#include "common/assert.h"
+#include "core/file_sys/vfs/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_static.h"
+
+namespace FileSys {
+
+ConcatenatedVfsFile::ConcatenatedVfsFile(std::string&& name_, ConcatenationMap&& concatenation_map_)
+    : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) {
+    DEBUG_ASSERT(this->VerifyContinuity());
+}
+
+bool ConcatenatedVfsFile::VerifyContinuity() const {
+    u64 last_offset = 0;
+    for (auto& entry : concatenation_map) {
+        if (entry.offset != last_offset) {
+            return false;
+        }
+
+        last_offset = entry.offset + entry.file->GetSize();
+    }
+
+    return true;
+}
+
+ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
+
+VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::string&& name,
+                                                      std::vector<VirtualFile>&& files) {
+    // Fold trivial cases.
+    if (files.empty()) {
+        return nullptr;
+    }
+    if (files.size() == 1) {
+        return files.front();
+    }
+
+    // Make the concatenation map from the input.
+    std::vector<ConcatenationEntry> concatenation_map;
+    concatenation_map.reserve(files.size());
+    u64 last_offset = 0;
+
+    for (auto& file : files) {
+        const auto size = file->GetSize();
+
+        concatenation_map.emplace_back(ConcatenationEntry{
+            .offset = last_offset,
+            .file = std::move(file),
+        });
+
+        last_offset += size;
+    }
+
+    return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
+}
+
+VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(
+    u8 filler_byte, std::string&& name, std::vector<std::pair<u64, VirtualFile>>&& files) {
+    // Fold trivial cases.
+    if (files.empty()) {
+        return nullptr;
+    }
+    if (files.size() == 1) {
+        return files.begin()->second;
+    }
+
+    // Make the concatenation map from the input.
+    std::vector<ConcatenationEntry> concatenation_map;
+
+    concatenation_map.reserve(files.size());
+    u64 last_offset = 0;
+
+    // Iteration of a multimap is ordered, so offset will be strictly non-decreasing.
+    for (auto& [offset, file] : files) {
+        const auto size = file->GetSize();
+
+        if (offset > last_offset) {
+            concatenation_map.emplace_back(ConcatenationEntry{
+                .offset = last_offset,
+                .file = std::make_shared<StaticVfsFile>(filler_byte, offset - last_offset),
+            });
+        }
+
+        concatenation_map.emplace_back(ConcatenationEntry{
+            .offset = offset,
+            .file = std::move(file),
+        });
+
+        last_offset = offset + size;
+    }
+
+    return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
+}
+
+std::string ConcatenatedVfsFile::GetName() const {
+    if (concatenation_map.empty()) {
+        return "";
+    }
+    if (!name.empty()) {
+        return name;
+    }
+    return concatenation_map.front().file->GetName();
+}
+
+std::size_t ConcatenatedVfsFile::GetSize() const {
+    if (concatenation_map.empty()) {
+        return 0;
+    }
+    return concatenation_map.back().offset + concatenation_map.back().file->GetSize();
+}
+
+bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
+    return false;
+}
+
+VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
+    if (concatenation_map.empty()) {
+        return nullptr;
+    }
+    return concatenation_map.front().file->GetContainingDirectory();
+}
+
+bool ConcatenatedVfsFile::IsWritable() const {
+    return false;
+}
+
+bool ConcatenatedVfsFile::IsReadable() const {
+    return true;
+}
+
+std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
+    const ConcatenationEntry key{
+        .offset = offset,
+        .file = nullptr,
+    };
+
+    // Read nothing if the map is empty.
+    if (concatenation_map.empty()) {
+        return 0;
+    }
+
+    // Binary search to find the iterator to the first position we can check.
+    // It must exist, since we are not empty and are comparing unsigned integers.
+    auto it = std::prev(std::upper_bound(concatenation_map.begin(), concatenation_map.end(), key));
+    u64 cur_length = length;
+    u64 cur_offset = offset;
+
+    while (cur_length > 0 && it != concatenation_map.end()) {
+        // Check if we can read the file at this position.
+        const auto& file = it->file;
+        const u64 map_offset = it->offset;
+        const u64 file_size = file->GetSize();
+
+        if (cur_offset > map_offset + file_size) {
+            // Entirely out of bounds read.
+            break;
+        }
+
+        // Read the file at this position.
+        const u64 file_seek = cur_offset - map_offset;
+        const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
+        const u64 actual_read_size =
+            file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
+
+        // Update tracking.
+        cur_offset += actual_read_size;
+        cur_length -= actual_read_size;
+        it++;
+
+        // If we encountered a short read, we're done.
+        if (actual_read_size < intended_read_size) {
+            break;
+        }
+    }
+
+    return cur_offset - offset;
+}
+
+std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
+    return 0;
+}
+
+bool ConcatenatedVfsFile::Rename(std::string_view new_name) {
+    return false;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_concat.h b/src/core/file_sys/vfs/vfs_concat.h
new file mode 100755
index 000000000..6d12af762
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_concat.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <compare>
+#include <map>
+#include <memory>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
+// read-only.
+class ConcatenatedVfsFile : public VfsFile {
+private:
+    struct ConcatenationEntry {
+        u64 offset;
+        VirtualFile file;
+
+        auto operator<=>(const ConcatenationEntry& other) const {
+            return this->offset <=> other.offset;
+        }
+    };
+    using ConcatenationMap = std::vector<ConcatenationEntry>;
+
+    explicit ConcatenatedVfsFile(std::string&& name,
+                                 std::vector<ConcatenationEntry>&& concatenation_map);
+    bool VerifyContinuity() const;
+
+public:
+    ~ConcatenatedVfsFile() override;
+
+    /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
+    static VirtualFile MakeConcatenatedFile(std::string&& name, std::vector<VirtualFile>&& files);
+
+    /// Convenience function that turns a map of offsets to files into a concatenated file, filling
+    /// gaps with a given filler byte.
+    static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::string&& name,
+                                            std::vector<std::pair<u64, VirtualFile>>&& files);
+
+    std::string GetName() const override;
+    std::size_t GetSize() const override;
+    bool Resize(std::size_t new_size) override;
+    VirtualDir GetContainingDirectory() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+    std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+    bool Rename(std::string_view new_name) override;
+
+private:
+    ConcatenationMap concatenation_map;
+    std::string name;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_layered.cpp b/src/core/file_sys/vfs/vfs_layered.cpp
new file mode 100755
index 000000000..47b2a3c78
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_layered.cpp
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <set>
+#include <unordered_set>
+#include <utility>
+#include "core/file_sys/vfs/vfs_layered.h"
+
+namespace FileSys {
+
+LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_)
+    : dirs(std::move(dirs_)), name(std::move(name_)) {}
+
+LayeredVfsDirectory::~LayeredVfsDirectory() = default;
+
+VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
+                                                     std::string name) {
+    if (dirs.empty())
+        return nullptr;
+    if (dirs.size() == 1)
+        return dirs[0];
+
+    return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
+}
+
+VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
+    for (const auto& layer : dirs) {
+        const auto file = layer->GetFileRelative(path);
+        if (file != nullptr)
+            return file;
+    }
+
+    return nullptr;
+}
+
+VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
+    std::vector<VirtualDir> out;
+    for (const auto& layer : dirs) {
+        auto dir = layer->GetDirectoryRelative(path);
+        if (dir != nullptr) {
+            out.emplace_back(std::move(dir));
+        }
+    }
+
+    return MakeLayeredDirectory(std::move(out));
+}
+
+VirtualFile LayeredVfsDirectory::GetFile(std::string_view file_name) const {
+    return GetFileRelative(file_name);
+}
+
+VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view subdir_name) const {
+    return GetDirectoryRelative(subdir_name);
+}
+
+std::string LayeredVfsDirectory::GetFullPath() const {
+    return dirs[0]->GetFullPath();
+}
+
+std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
+    std::vector<VirtualFile> out;
+    std::unordered_set<std::string> out_names;
+
+    for (const auto& layer : dirs) {
+        for (auto& file : layer->GetFiles()) {
+            const auto [it, is_new] = out_names.emplace(file->GetName());
+            if (is_new) {
+                out.emplace_back(std::move(file));
+            }
+        }
+    }
+
+    return out;
+}
+
+std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
+    std::vector<VirtualDir> out;
+    std::unordered_set<std::string> out_names;
+
+    for (const auto& layer : dirs) {
+        for (const auto& sd : layer->GetSubdirectories()) {
+            out_names.emplace(sd->GetName());
+        }
+    }
+
+    out.reserve(out_names.size());
+    for (const auto& subdir : out_names) {
+        out.emplace_back(GetSubdirectory(subdir));
+    }
+
+    return out;
+}
+
+bool LayeredVfsDirectory::IsWritable() const {
+    return false;
+}
+
+bool LayeredVfsDirectory::IsReadable() const {
+    return true;
+}
+
+std::string LayeredVfsDirectory::GetName() const {
+    return name.empty() ? dirs[0]->GetName() : name;
+}
+
+VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
+    return dirs[0]->GetParentDirectory();
+}
+
+VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
+    return nullptr;
+}
+
+VirtualFile LayeredVfsDirectory::CreateFile(std::string_view file_name) {
+    return nullptr;
+}
+
+bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
+    return false;
+}
+
+bool LayeredVfsDirectory::DeleteFile(std::string_view file_name) {
+    return false;
+}
+
+bool LayeredVfsDirectory::Rename(std::string_view new_name) {
+    name = new_name;
+    return true;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_layered.h b/src/core/file_sys/vfs/vfs_layered.h
new file mode 100755
index 000000000..0027ffa9a
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_layered.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
+// one and falling back to the one after. The highest priority directory (overwrites all others)
+// should be element 0 in the dirs vector.
+class LayeredVfsDirectory : public VfsDirectory {
+    explicit LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_);
+
+public:
+    ~LayeredVfsDirectory() override;
+
+    /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
+    static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
+
+    VirtualFile GetFileRelative(std::string_view path) const override;
+    VirtualDir GetDirectoryRelative(std::string_view path) const override;
+    VirtualFile GetFile(std::string_view file_name) const override;
+    VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
+    std::string GetFullPath() const override;
+
+    std::vector<VirtualFile> GetFiles() const override;
+    std::vector<VirtualDir> GetSubdirectories() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::string GetName() const override;
+    VirtualDir GetParentDirectory() const override;
+    VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
+    VirtualFile CreateFile(std::string_view file_name) override;
+    bool DeleteSubdirectory(std::string_view subdir_name) override;
+    bool DeleteFile(std::string_view file_name) override;
+    bool Rename(std::string_view new_name) override;
+
+private:
+    std::vector<VirtualDir> dirs;
+    std::string name;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_offset.cpp b/src/core/file_sys/vfs/vfs_offset.cpp
new file mode 100755
index 000000000..1a37d2670
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_offset.cpp
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+
+#include "core/file_sys/vfs/vfs_offset.h"
+
+namespace FileSys {
+
+OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
+                             std::string name_, VirtualDir parent_)
+    : file(file_), offset(offset_), size(size_), name(std::move(name_)),
+      parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
+
+OffsetVfsFile::~OffsetVfsFile() = default;
+
+std::string OffsetVfsFile::GetName() const {
+    return name.empty() ? file->GetName() : name;
+}
+
+std::size_t OffsetVfsFile::GetSize() const {
+    return size;
+}
+
+bool OffsetVfsFile::Resize(std::size_t new_size) {
+    if (offset + new_size < file->GetSize()) {
+        size = new_size;
+    } else {
+        auto res = file->Resize(offset + new_size);
+        if (!res)
+            return false;
+        size = new_size;
+    }
+
+    return true;
+}
+
+VirtualDir OffsetVfsFile::GetContainingDirectory() const {
+    return parent;
+}
+
+bool OffsetVfsFile::IsWritable() const {
+    return file->IsWritable();
+}
+
+bool OffsetVfsFile::IsReadable() const {
+    return file->IsReadable();
+}
+
+std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const {
+    return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
+}
+
+std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) {
+    return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
+}
+
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+    if (r_offset >= size) {
+        return std::nullopt;
+    }
+
+    return file->ReadByte(offset + r_offset);
+}
+
+std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
+    return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
+}
+
+std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
+    return file->ReadBytes(size, offset);
+}
+
+bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) {
+    if (r_offset < size)
+        return file->WriteByte(data, offset + r_offset);
+
+    return false;
+}
+
+std::size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, std::size_t r_offset) {
+    return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
+}
+
+bool OffsetVfsFile::Rename(std::string_view new_name) {
+    return file->Rename(new_name);
+}
+
+std::size_t OffsetVfsFile::GetOffset() const {
+    return offset;
+}
+
+std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const {
+    return std::clamp(r_size, std::size_t{0}, size - r_offset);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_offset.h b/src/core/file_sys/vfs/vfs_offset.h
new file mode 100755
index 000000000..4abe41d8e
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_offset.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
+// Similar to seeking to an offset.
+// If the file is writable, operations that would write past the end of the offset file will expand
+// the size of this wrapper.
+class OffsetVfsFile : public VfsFile {
+public:
+    OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
+                  std::string new_name = "", VirtualDir new_parent = nullptr);
+    ~OffsetVfsFile() override;
+
+    std::string GetName() const override;
+    std::size_t GetSize() const override;
+    bool Resize(std::size_t new_size) override;
+    VirtualDir GetContainingDirectory() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+    std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+    std::optional<u8> ReadByte(std::size_t offset) const override;
+    std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
+    std::vector<u8> ReadAllBytes() const override;
+    bool WriteByte(u8 data, std::size_t offset) override;
+    std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset) override;
+
+    bool Rename(std::string_view new_name) override;
+
+    std::size_t GetOffset() const;
+
+private:
+    std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
+
+    VirtualFile file;
+    std::size_t offset;
+    std::size_t size;
+    std::string name;
+    VirtualDir parent;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp
new file mode 100755
index 000000000..627d5d251
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.cpp
@@ -0,0 +1,527 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <utility>
+#include "common/assert.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_real.h"
+
+// For FileTimeStampRaw
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#define stat _stat64
+#endif
+
+namespace FileSys {
+
+namespace FS = Common::FS;
+
+namespace {
+
+constexpr size_t MaxOpenFiles = 512;
+
+constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
+    switch (mode) {
+    case OpenMode::Read:
+        return FS::FileAccessMode::Read;
+    case OpenMode::Write:
+    case OpenMode::ReadWrite:
+    case OpenMode::AllowAppend:
+    case OpenMode::All:
+        return FS::FileAccessMode::ReadWrite;
+    default:
+        return {};
+    }
+}
+
+} // Anonymous namespace
+
+RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
+RealVfsFilesystem::~RealVfsFilesystem() = default;
+
+std::string RealVfsFilesystem::GetName() const {
+    return "Real";
+}
+
+bool RealVfsFilesystem::IsReadable() const {
+    return true;
+}
+
+bool RealVfsFilesystem::IsWritable() const {
+    return true;
+}
+
+VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    if (!FS::Exists(path)) {
+        return VfsEntryType::None;
+    }
+    if (FS::IsDir(path)) {
+        return VfsEntryType::Directory;
+    }
+
+    return VfsEntryType::File;
+}
+
+VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
+                                                 OpenMode perms) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    std::scoped_lock lk{list_lock};
+
+    if (auto it = cache.find(path); it != cache.end()) {
+        if (auto file = it->second.lock(); file) {
+            return file;
+        }
+    }
+
+    if (!size && !FS::IsFile(path)) {
+        return nullptr;
+    }
+
+    auto reference = std::make_unique<FileReference>();
+    this->InsertReferenceIntoListLocked(*reference);
+
+    auto file = std::shared_ptr<RealVfsFile>(
+        new RealVfsFile(*this, std::move(reference), path, perms, size));
+    cache[path] = file;
+
+    return file;
+}
+
+VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
+    return OpenFileFromEntry(path_, {}, perms);
+}
+
+VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    {
+        std::scoped_lock lk{list_lock};
+        cache.erase(path);
+    }
+
+    // Current usages of CreateFile expect to delete the contents of an existing file.
+    if (FS::IsFile(path)) {
+        FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
+
+        if (!temp.IsOpen()) {
+            return nullptr;
+        }
+
+        temp.Close();
+
+        return OpenFile(path, perms);
+    }
+
+    if (!FS::NewFile(path)) {
+        return nullptr;
+    }
+
+    return OpenFile(path, perms);
+}
+
+VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+    // Unused
+    return nullptr;
+}
+
+VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
+    const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+    const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+    {
+        std::scoped_lock lk{list_lock};
+        cache.erase(old_path);
+        cache.erase(new_path);
+    }
+    if (!FS::RenameFile(old_path, new_path)) {
+        return nullptr;
+    }
+    return OpenFile(new_path, OpenMode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    {
+        std::scoped_lock lk{list_lock};
+        cache.erase(path);
+    }
+    return FS::RemoveFile(path);
+}
+
+VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    if (!FS::CreateDirs(path)) {
+        return nullptr;
+    }
+    return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
+                                            std::string_view new_path_) {
+    // Unused
+    return nullptr;
+}
+
+VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
+                                            std::string_view new_path_) {
+    const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+    const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+
+    if (!FS::RenameDir(old_path, new_path)) {
+        return nullptr;
+    }
+    return OpenDirectory(new_path, OpenMode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
+    const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+    return FS::RemoveDirRecursively(path);
+}
+
+std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::string& path,
+                                                                 OpenMode perms,
+                                                                 FileReference& reference) {
+    std::unique_lock lk{list_lock};
+
+    // Temporarily remove from list.
+    this->RemoveReferenceFromListLocked(reference);
+
+    // Restore file if needed.
+    if (!reference.file) {
+        this->EvictSingleReferenceLocked();
+
+        reference.file =
+            FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
+        if (reference.file) {
+            num_open_files++;
+        }
+    }
+
+    // Reinsert into list.
+    this->InsertReferenceIntoListLocked(reference);
+
+    return lk;
+}
+
+void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
+    std::scoped_lock lk{list_lock};
+
+    // Remove from list.
+    this->RemoveReferenceFromListLocked(*reference);
+
+    // Close the file.
+    if (reference->file) {
+        reference->file.reset();
+        num_open_files--;
+    }
+}
+
+void RealVfsFilesystem::EvictSingleReferenceLocked() {
+    if (num_open_files < MaxOpenFiles || open_references.empty()) {
+        return;
+    }
+
+    // Get and remove from list.
+    auto& reference = open_references.back();
+    this->RemoveReferenceFromListLocked(reference);
+
+    // Close the file.
+    if (reference.file) {
+        reference.file.reset();
+        num_open_files--;
+    }
+
+    // Reinsert into closed list.
+    this->InsertReferenceIntoListLocked(reference);
+}
+
+void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) {
+    if (reference.file) {
+        open_references.push_front(reference);
+    } else {
+        closed_references.push_front(reference);
+    }
+}
+
+void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) {
+    if (reference.file) {
+        open_references.erase(open_references.iterator_to(reference));
+    } else {
+        closed_references.erase(closed_references.iterator_to(reference));
+    }
+}
+
+RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
+                         const std::string& path_, OpenMode perms_, std::optional<u64> size_)
+    : base(base_), reference(std::move(reference_)), path(path_),
+      parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)),
+      size(size_), perms(perms_) {}
+
+RealVfsFile::~RealVfsFile() {
+    base.DropReference(std::move(reference));
+}
+
+std::string RealVfsFile::GetName() const {
+    return path_components.empty() ? "" : std::string(path_components.back());
+}
+
+std::size_t RealVfsFile::GetSize() const {
+    if (size) {
+        return *size;
+    }
+    auto lk = base.RefreshReference(path, perms, *reference);
+    return reference->file ? reference->file->GetSize() : 0;
+}
+
+bool RealVfsFile::Resize(std::size_t new_size) {
+    size.reset();
+    auto lk = base.RefreshReference(path, perms, *reference);
+    return reference->file ? reference->file->SetSize(new_size) : false;
+}
+
+VirtualDir RealVfsFile::GetContainingDirectory() const {
+    return base.OpenDirectory(parent_path, perms);
+}
+
+bool RealVfsFile::IsWritable() const {
+    return True(perms & OpenMode::Write);
+}
+
+bool RealVfsFile::IsReadable() const {
+    return True(perms & OpenMode::Read);
+}
+
+std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
+    auto lk = base.RefreshReference(path, perms, *reference);
+    if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
+        return 0;
+    }
+    return reference->file->ReadSpan(std::span{data, length});
+}
+
+std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
+    size.reset();
+    auto lk = base.RefreshReference(path, perms, *reference);
+    if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
+        return 0;
+    }
+    return reference->file->WriteSpan(std::span{data, length});
+}
+
+bool RealVfsFile::Rename(std::string_view name) {
+    return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
+}
+
+// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
+// constexpr' because there is a compile error in the branch not used.
+
+template <>
+std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
+    if (perms == OpenMode::AllowAppend) {
+        return {};
+    }
+
+    std::vector<VirtualFile> out;
+
+    const FS::DirEntryCallable callback = [this,
+                                           &out](const std::filesystem::directory_entry& entry) {
+        const auto full_path_string = FS::PathToUTF8String(entry.path());
+
+        out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms));
+
+        return true;
+    };
+
+    FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File);
+
+    return out;
+}
+
+template <>
+std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
+    if (perms == OpenMode::AllowAppend) {
+        return {};
+    }
+
+    std::vector<VirtualDir> out;
+
+    const FS::DirEntryCallable callback = [this,
+                                           &out](const std::filesystem::directory_entry& entry) {
+        const auto full_path_string = FS::PathToUTF8String(entry.path());
+
+        out.emplace_back(base.OpenDirectory(full_path_string, perms));
+
+        return true;
+    };
+
+    FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory);
+
+    return out;
+}
+
+RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_,
+                                   OpenMode perms_)
+    : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
+      path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) {
+    if (!FS::Exists(path) && True(perms & OpenMode::Write)) {
+        void(FS::CreateDirs(path));
+    }
+}
+
+RealVfsDirectory::~RealVfsDirectory() = default;
+
+VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
+    const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+    if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
+        return nullptr;
+    }
+    return base.OpenFile(full_path, perms);
+}
+
+VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
+    const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+    if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
+        return nullptr;
+    }
+    return base.OpenDirectory(full_path, perms);
+}
+
+VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
+    return GetFileRelative(name);
+}
+
+VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
+    return GetDirectoryRelative(name);
+}
+
+VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
+    const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+    if (!FS::CreateParentDirs(full_path)) {
+        return nullptr;
+    }
+    return base.CreateFile(full_path, perms);
+}
+
+VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) {
+    const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+    return base.CreateDirectory(full_path, perms);
+}
+
+bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+    const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
+    return base.DeleteDirectory(full_path);
+}
+
+std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
+    return IterateEntries<RealVfsFile, VfsFile>();
+}
+
+FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const {
+    const auto full_path = FS::SanitizePath(path + '/' + std::string(path_));
+    const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)};
+    struct stat file_status;
+
+#ifdef _WIN32
+    const auto stat_result = _wstat64(fs_path.c_str(), &file_status);
+#else
+    const auto stat_result = stat(fs_path.c_str(), &file_status);
+#endif
+
+    if (stat_result != 0) {
+        return {};
+    }
+
+    return {
+        .created{static_cast<u64>(file_status.st_ctime)},
+        .accessed{static_cast<u64>(file_status.st_atime)},
+        .modified{static_cast<u64>(file_status.st_mtime)},
+    };
+}
+
+std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
+    return IterateEntries<RealVfsDirectory, VfsDirectory>();
+}
+
+bool RealVfsDirectory::IsWritable() const {
+    return True(perms & OpenMode::Write);
+}
+
+bool RealVfsDirectory::IsReadable() const {
+    return True(perms & OpenMode::Read);
+}
+
+std::string RealVfsDirectory::GetName() const {
+    return path_components.empty() ? "" : std::string(path_components.back());
+}
+
+VirtualDir RealVfsDirectory::GetParentDirectory() const {
+    if (path_components.size() <= 1) {
+        return nullptr;
+    }
+
+    return base.OpenDirectory(parent_path, perms);
+}
+
+VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
+    const std::string subdir_path = (path + '/').append(name);
+    return base.CreateDirectory(subdir_path, perms);
+}
+
+VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
+    const std::string file_path = (path + '/').append(name);
+    return base.CreateFile(file_path, perms);
+}
+
+bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
+    const std::string subdir_path = (path + '/').append(name);
+    return base.DeleteDirectory(subdir_path);
+}
+
+bool RealVfsDirectory::DeleteFile(std::string_view name) {
+    const std::string file_path = (path + '/').append(name);
+    return base.DeleteFile(file_path);
+}
+
+bool RealVfsDirectory::Rename(std::string_view name) {
+    const std::string new_name = (parent_path + '/').append(name);
+    return base.MoveFile(path, new_name) != nullptr;
+}
+
+std::string RealVfsDirectory::GetFullPath() const {
+    auto out = path;
+    std::replace(out.begin(), out.end(), '\\', '/');
+    return out;
+}
+
+std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
+    if (perms == OpenMode::AllowAppend) {
+        return {};
+    }
+
+    std::map<std::string, VfsEntryType, std::less<>> out;
+
+    const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
+        const auto filename = FS::PathToUTF8String(entry.path().filename());
+        out.insert_or_assign(filename,
+                             entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File);
+        return true;
+    };
+
+    FS::IterateDirEntries(path, callback);
+
+    return out;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h
new file mode 100755
index 000000000..5c2172cce
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.h
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <string_view>
+#include "common/intrusive_list.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/vfs/vfs.h"
+
+namespace Common::FS {
+class IOFile;
+}
+
+namespace FileSys {
+
+struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
+    std::shared_ptr<Common::FS::IOFile> file{};
+};
+
+class RealVfsFile;
+class RealVfsDirectory;
+
+class RealVfsFilesystem : public VfsFilesystem {
+public:
+    RealVfsFilesystem();
+    ~RealVfsFilesystem() override;
+
+    std::string GetName() const override;
+    bool IsReadable() const override;
+    bool IsWritable() const override;
+    VfsEntryType GetEntryType(std::string_view path) const override;
+    VirtualFile OpenFile(std::string_view path, OpenMode perms = OpenMode::Read) override;
+    VirtualFile CreateFile(std::string_view path, OpenMode perms = OpenMode::ReadWrite) override;
+    VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override;
+    VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override;
+    bool DeleteFile(std::string_view path) override;
+    VirtualDir OpenDirectory(std::string_view path, OpenMode perms = OpenMode::Read) override;
+    VirtualDir CreateDirectory(std::string_view path,
+                               OpenMode perms = OpenMode::ReadWrite) override;
+    VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override;
+    VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override;
+    bool DeleteDirectory(std::string_view path) override;
+
+private:
+    using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
+    std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
+    ReferenceListType open_references;
+    ReferenceListType closed_references;
+    std::mutex list_lock;
+    size_t num_open_files{};
+
+private:
+    friend class RealVfsFile;
+    std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
+                                                  FileReference& reference);
+    void DropReference(std::unique_ptr<FileReference>&& reference);
+
+private:
+    friend class RealVfsDirectory;
+    VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size,
+                                  OpenMode perms = OpenMode::Read);
+
+private:
+    void EvictSingleReferenceLocked();
+    void InsertReferenceIntoListLocked(FileReference& reference);
+    void RemoveReferenceFromListLocked(FileReference& reference);
+};
+
+// An implementation of VfsFile that represents a file on the user's computer.
+class RealVfsFile : public VfsFile {
+    friend class RealVfsDirectory;
+    friend class RealVfsFilesystem;
+
+public:
+    ~RealVfsFile() override;
+
+    std::string GetName() const override;
+    std::size_t GetSize() const override;
+    bool Resize(std::size_t new_size) override;
+    VirtualDir GetContainingDirectory() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+    std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+    bool Rename(std::string_view name) override;
+
+private:
+    RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
+                const std::string& path, OpenMode perms = OpenMode::Read,
+                std::optional<u64> size = {});
+
+    RealVfsFilesystem& base;
+    std::unique_ptr<FileReference> reference;
+    std::string path;
+    std::string parent_path;
+    std::vector<std::string> path_components;
+    std::optional<u64> size;
+    OpenMode perms;
+};
+
+// An implementation of VfsDirectory that represents a directory on the user's computer.
+class RealVfsDirectory : public VfsDirectory {
+    friend class RealVfsFilesystem;
+
+public:
+    ~RealVfsDirectory() override;
+
+    VirtualFile GetFileRelative(std::string_view relative_path) const override;
+    VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
+    VirtualFile GetFile(std::string_view name) const override;
+    VirtualDir GetSubdirectory(std::string_view name) const override;
+    VirtualFile CreateFileRelative(std::string_view relative_path) override;
+    VirtualDir CreateDirectoryRelative(std::string_view relative_path) override;
+    bool DeleteSubdirectoryRecursive(std::string_view name) override;
+    std::vector<VirtualFile> GetFiles() const override;
+    FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override;
+    std::vector<VirtualDir> GetSubdirectories() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::string GetName() const override;
+    VirtualDir GetParentDirectory() const override;
+    VirtualDir CreateSubdirectory(std::string_view name) override;
+    VirtualFile CreateFile(std::string_view name) override;
+    bool DeleteSubdirectory(std::string_view name) override;
+    bool DeleteFile(std::string_view name) override;
+    bool Rename(std::string_view name) override;
+    std::string GetFullPath() const override;
+    std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
+
+private:
+    RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
+                     OpenMode perms = OpenMode::Read);
+
+    template <typename T, typename R>
+    std::vector<std::shared_ptr<R>> IterateEntries() const;
+
+    RealVfsFilesystem& base;
+    std::string path;
+    std::string parent_path;
+    std::vector<std::string> path_components;
+    OpenMode perms;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_static.h b/src/core/file_sys/vfs/vfs_static.h
new file mode 100755
index 000000000..bb53560ac
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_static.h
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+class StaticVfsFile : public VfsFile {
+public:
+    explicit StaticVfsFile(u8 value_, std::size_t size_ = 0, std::string name_ = "",
+                           VirtualDir parent_ = nullptr)
+        : value{value_}, size{size_}, name{std::move(name_)}, parent{std::move(parent_)} {}
+
+    std::string GetName() const override {
+        return name;
+    }
+
+    std::size_t GetSize() const override {
+        return size;
+    }
+
+    bool Resize(std::size_t new_size) override {
+        size = new_size;
+        return true;
+    }
+
+    VirtualDir GetContainingDirectory() const override {
+        return parent;
+    }
+
+    bool IsWritable() const override {
+        return false;
+    }
+
+    bool IsReadable() const override {
+        return true;
+    }
+
+    std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
+        const auto read = std::min(length, size - offset);
+        std::fill(data, data + read, value);
+        return read;
+    }
+
+    std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
+        return 0;
+    }
+
+    std::optional<u8> ReadByte(std::size_t offset) const override {
+        if (offset >= size) {
+            return std::nullopt;
+        }
+
+        return value;
+    }
+
+    std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
+        const auto read = std::min(length, size - offset);
+        return std::vector<u8>(read, value);
+    }
+
+    bool Rename(std::string_view new_name) override {
+        name = new_name;
+        return true;
+    }
+
+private:
+    u8 value;
+    std::size_t size;
+    std::string name;
+    VirtualDir parent;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_types.h b/src/core/file_sys/vfs/vfs_types.h
new file mode 100755
index 000000000..4a583ed64
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_types.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace FileSys {
+
+class VfsDirectory;
+class VfsFile;
+class VfsFilesystem;
+
+// Declarations for Vfs* pointer types
+
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+
+struct FileTimeStampRaw {
+    u64 created{};
+    u64 accessed{};
+    u64 modified{};
+    u64 padding{};
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_vector.cpp b/src/core/file_sys/vfs/vfs_vector.cpp
new file mode 100755
index 000000000..0d54461c8
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_vector.cpp
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+#include "core/file_sys/vfs/vfs_vector.h"
+
+namespace FileSys {
+VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name_, VirtualDir parent_)
+    : data(std::move(initial_data)), parent(std::move(parent_)), name(std::move(name_)) {}
+
+VectorVfsFile::~VectorVfsFile() = default;
+
+std::string VectorVfsFile::GetName() const {
+    return name;
+}
+
+size_t VectorVfsFile::GetSize() const {
+    return data.size();
+}
+
+bool VectorVfsFile::Resize(size_t new_size) {
+    data.resize(new_size);
+    return true;
+}
+
+VirtualDir VectorVfsFile::GetContainingDirectory() const {
+    return parent;
+}
+
+bool VectorVfsFile::IsWritable() const {
+    return true;
+}
+
+bool VectorVfsFile::IsReadable() const {
+    return true;
+}
+
+std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
+    const auto read = std::min(length, data.size() - offset);
+    std::memcpy(data_, data.data() + offset, read);
+    return read;
+}
+
+std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
+    if (offset + length > data.size())
+        data.resize(offset + length);
+    const auto write = std::min(length, data.size() - offset);
+    std::memcpy(data.data() + offset, data_, write);
+    return write;
+}
+
+bool VectorVfsFile::Rename(std::string_view name_) {
+    name = name_;
+    return true;
+}
+
+void VectorVfsFile::Assign(std::vector<u8> new_data) {
+    data = std::move(new_data);
+}
+
+VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
+                                       std::vector<VirtualDir> dirs_, std::string name_,
+                                       VirtualDir parent_)
+    : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
+      name(std::move(name_)) {}
+
+VectorVfsDirectory::~VectorVfsDirectory() = default;
+
+std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
+    return files;
+}
+
+std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
+    return dirs;
+}
+
+bool VectorVfsDirectory::IsWritable() const {
+    return false;
+}
+
+bool VectorVfsDirectory::IsReadable() const {
+    return true;
+}
+
+std::string VectorVfsDirectory::GetName() const {
+    return name;
+}
+
+VirtualDir VectorVfsDirectory::GetParentDirectory() const {
+    return parent;
+}
+
+template <typename T>
+static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) {
+    const auto iter =
+        std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; });
+    if (iter == vec.end())
+        return false;
+
+    vec.erase(iter);
+    return true;
+}
+
+bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
+    return FindAndRemoveVectorElement(dirs, subdir_name);
+}
+
+bool VectorVfsDirectory::DeleteFile(std::string_view file_name) {
+    return FindAndRemoveVectorElement(files, file_name);
+}
+
+bool VectorVfsDirectory::Rename(std::string_view name_) {
+    name = name_;
+    return true;
+}
+
+VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
+    return nullptr;
+}
+
+VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) {
+    return nullptr;
+}
+
+void VectorVfsDirectory::AddFile(VirtualFile file) {
+    files.push_back(std::move(file));
+}
+
+void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
+    dirs.push_back(std::move(dir));
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_vector.h b/src/core/file_sys/vfs/vfs_vector.h
new file mode 100755
index 000000000..587187dd2
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_vector.h
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// An implementation of VfsFile that is backed by a statically-sized array
+template <std::size_t size>
+class ArrayVfsFile : public VfsFile {
+public:
+    explicit ArrayVfsFile(const std::array<u8, size>& data_, std::string name_ = "",
+                          VirtualDir parent_ = nullptr)
+        : data(data_), name(std::move(name_)), parent(std::move(parent_)) {}
+
+    std::string GetName() const override {
+        return name;
+    }
+
+    std::size_t GetSize() const override {
+        return size;
+    }
+
+    bool Resize(std::size_t new_size) override {
+        return false;
+    }
+
+    VirtualDir GetContainingDirectory() const override {
+        return parent;
+    }
+
+    bool IsWritable() const override {
+        return false;
+    }
+
+    bool IsReadable() const override {
+        return true;
+    }
+
+    std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
+        const auto read = std::min(length, size - offset);
+        std::memcpy(data_, data.data() + offset, read);
+        return read;
+    }
+
+    std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override {
+        return 0;
+    }
+
+    bool Rename(std::string_view new_name) override {
+        name = new_name;
+        return true;
+    }
+
+private:
+    std::array<u8, size> data;
+    std::string name;
+    VirtualDir parent;
+};
+
+template <std::size_t Size, typename... Args>
+std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data,
+                                                  Args&&... args) {
+    return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...);
+}
+
+// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
+class VectorVfsFile : public VfsFile {
+public:
+    explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name_ = "",
+                           VirtualDir parent_ = nullptr);
+    ~VectorVfsFile() override;
+
+    std::string GetName() const override;
+    std::size_t GetSize() const override;
+    bool Resize(std::size_t new_size) override;
+    VirtualDir GetContainingDirectory() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+    std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+    bool Rename(std::string_view name) override;
+
+    virtual void Assign(std::vector<u8> new_data);
+
+private:
+    std::vector<u8> data;
+    VirtualDir parent;
+    std::string name;
+};
+
+// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
+// Vector data is supplied upon construction.
+class VectorVfsDirectory : public VfsDirectory {
+public:
+    explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
+                                std::vector<VirtualDir> dirs = {}, std::string name = "",
+                                VirtualDir parent = nullptr);
+    ~VectorVfsDirectory() override;
+
+    std::vector<VirtualFile> GetFiles() const override;
+    std::vector<VirtualDir> GetSubdirectories() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    std::string GetName() const override;
+    VirtualDir GetParentDirectory() const override;
+    bool DeleteSubdirectory(std::string_view subdir_name) override;
+    bool DeleteFile(std::string_view file_name) override;
+    bool Rename(std::string_view name) override;
+    VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
+    VirtualFile CreateFile(std::string_view file_name) override;
+
+    virtual void AddFile(VirtualFile file);
+    virtual void AddDirectory(VirtualDir dir);
+
+private:
+    std::vector<VirtualFile> files;
+    std::vector<VirtualDir> dirs;
+
+    VirtualDir parent;
+    std::string name;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index 7e05cb5d1..801d2a62b 100755
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -17,7 +17,7 @@
 #include "core/crypto/key_manager.h"
 #include "core/crypto/xts_encryption_layer.h"
 #include "core/file_sys/content_archive.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/file_sys/xts_archive.h"
 #include "core/loader/loader.h"
 
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index a20273862..87ed9be8e 100755
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -8,7 +8,7 @@
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/crypto/key_manager.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp
index 87adcdd1d..f5125ffd6 100755
--- a/src/core/frontend/applets/error.cpp
+++ b/src/core/frontend/applets/error.cpp
@@ -12,7 +12,7 @@ void DefaultErrorApplet::Close() const {}
 
 void DefaultErrorApplet::ShowError(Result error, FinishedCallback finished) const {
     LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})",
-                 error.module.Value(), error.description.Value(), error.raw);
+                 error.GetModule(), error.GetDescription(), error.raw);
 }
 
 void DefaultErrorApplet::ShowErrorWithTimestamp(Result error, std::chrono::seconds time,
@@ -20,7 +20,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(Result error, std::chrono::secon
     LOG_CRITICAL(
         Service_Fatal,
         "Application requested error display: {:04X}-{:04X} (raw={:08X}) with timestamp={:016X}",
-        error.module.Value(), error.description.Value(), error.raw, time.count());
+        error.GetModule(), error.GetDescription(), error.raw, time.count());
 }
 
 void DefaultErrorApplet::ShowCustomErrorText(Result error, std::string main_text,
@@ -28,7 +28,7 @@ void DefaultErrorApplet::ShowCustomErrorText(Result error, std::string main_text
                                              FinishedCallback finished) const {
     LOG_CRITICAL(Service_Fatal,
                  "Application requested custom error with error_code={:04X}-{:04X} (raw={:08X})",
-                 error.module.Value(), error.description.Value(), error.raw);
+                 error.GetModule(), error.GetDescription(), error.raw);
     LOG_CRITICAL(Service_Fatal, "    Main Text: {}", main_text);
     LOG_CRITICAL(Service_Fatal, "    Detail Text: {}", detail_text);
 }
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index cfde4137f..a14e1372a 100755
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -189,14 +189,14 @@ enum class ErrorModule : u32 {
 union Result {
     u32 raw;
 
-    BitField<0, 9, ErrorModule> module;
-    BitField<9, 13, u32> description;
+    using Module = BitField<0, 9, ErrorModule>;
+    using Description = BitField<9, 13, u32>;
 
     Result() = default;
     constexpr explicit Result(u32 raw_) : raw(raw_) {}
 
     constexpr Result(ErrorModule module_, u32 description_)
-        : raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
+        : raw(Module::FormatValue(module_) | Description::FormatValue(description_)) {}
 
     [[nodiscard]] constexpr bool IsSuccess() const {
         return raw == 0;
@@ -211,7 +211,15 @@ union Result {
     }
 
     [[nodiscard]] constexpr u32 GetInnerValue() const {
-        return static_cast<u32>(module.Value()) | (description << module.bits);
+        return raw;
+    }
+
+    [[nodiscard]] constexpr ErrorModule GetModule() const {
+        return Module::ExtractValue(raw);
+    }
+
+    [[nodiscard]] constexpr u32 GetDescription() const {
+        return Description::ExtractValue(raw);
     }
 
     [[nodiscard]] constexpr bool Includes(Result result) const {
@@ -274,8 +282,9 @@ public:
     }
 
     [[nodiscard]] constexpr bool Includes(Result other) const {
-        return code.module == other.module && code.description <= other.description &&
-               other.description <= description_end;
+        return code.GetModule() == other.GetModule() &&
+               code.GetDescription() <= other.GetDescription() &&
+               other.GetDescription() <= description_end;
     }
 
 private:
@@ -330,6 +339,16 @@ constexpr bool EvaluateResultFailure(const Result& r) {
     return R_FAILED(r);
 }
 
+template <auto... R>
+constexpr bool EvaluateAnyResultIncludes(const Result& r) {
+    return ((r == R) || ...);
+}
+
+template <auto... R>
+constexpr bool EvaluateResultNotIncluded(const Result& r) {
+    return !EvaluateAnyResultIncludes<R...>(r);
+}
+
 template <typename T>
 constexpr void UpdateCurrentResultReference(T result_reference, Result result) = delete;
 // Intentionally not defined
@@ -371,6 +390,13 @@ constexpr void UpdateCurrentResultReference<const Result>(Result result_referenc
     DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__);                                     \
     ON_RESULT_SUCCESS_2
 
+#define ON_RESULT_INCLUDED_2(...)                                                                  \
+    ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateAnyResultIncludes<__VA_ARGS__>)
+
+#define ON_RESULT_INCLUDED(...)                                                                    \
+    DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__);                                     \
+    ON_RESULT_INCLUDED_2(__VA_ARGS__)
+
 constexpr inline Result __TmpCurrentResultReference = ResultSuccess;
 
 /// Returns a result.
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index e35c5f1a6..6ff346045 100755
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -27,8 +27,8 @@ struct ErrorCode {
 
     static constexpr ErrorCode FromResult(Result result) {
         return {
-            .error_category{2000 + static_cast<u32>(result.module.Value())},
-            .error_number{result.description.Value()},
+            .error_category{2000 + static_cast<u32>(result.GetModule())},
+            .error_number{result.GetDescription()},
         };
     }
 
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 4cec088b6..1ee1d7234 100755
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -9,13 +9,13 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/content_archive.h"
-#include "core/file_sys/mode.h"
+#include "core/file_sys/fs_filesystem.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/system_archive/system_archive.h"
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 #include "core/frontend/applets/web_browser.h"
 #include "core/hle/result.h"
 #include "core/hle/service/am/am.h"
@@ -213,7 +213,7 @@ void ExtractSharedFonts(Core::System& system) {
             std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
 
         const auto temp_dir = system.GetFilesystem()->CreateDirectory(
-            Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite);
+            Common::FS::PathToUTF8String(fonts_dir), FileSys::OpenMode::ReadWrite);
 
         const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
 
@@ -333,7 +333,7 @@ void WebBrowser::ExtractOfflineRomFS() {
     const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs);
 
     const auto temp_dir = system.GetFilesystem()->CreateDirectory(
-        Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite);
+        Common::FS::PathToUTF8String(offline_cache_dir), FileSys::OpenMode::ReadWrite);
 
     FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
 }
diff --git a/src/core/hle/service/am/applets/applet_web_browser.h b/src/core/hle/service/am/applets/applet_web_browser.h
index 7fb77c40f..e63eca84f 100755
--- a/src/core/hle/service/am/applets/applet_web_browser.h
+++ b/src/core/hle/service/am/applets/applet_web_browser.h
@@ -7,7 +7,7 @@
 #include <optional>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/hle/result.h"
 #include "core/hle/service/am/applets/applet_web_browser_types.h"
 #include "core/hle/service/am/applets/applets.h"
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index c8cc3bb44..191df61b5 100755
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -139,7 +139,8 @@ private:
                 ctx.WriteBufferC(performance_buffer.data(), performance_buffer.size(), 1);
             }
         } else {
-            LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description);
+            LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!",
+                      result.GetDescription());
         }
 
         IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 00c43d99b..50cc630ec 100755
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -8,7 +8,7 @@
 #include <string>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/hle/result.h"
 #include "core/hle/service/kernel_helpers.h"
 
diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp
index 4a58ba9a4..d03ebf485 100755
--- a/src/core/hle/service/bcat/bcat_module.cpp
+++ b/src/core/hle/service/bcat/bcat_module.cpp
@@ -8,7 +8,7 @@
 #include "common/settings.h"
 #include "common/string_util.h"
 #include "core/core.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/service/bcat/backend/backend.h"
 #include "core/hle/service/bcat/bcat.h"
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 023841346..00f934160 100755
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -202,14 +202,14 @@ Result IAlbumAccessorService::TranslateResult(Result in_result) {
     }
 
     if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
-        if (in_result.description - 0x514 < 100) {
+        if (in_result.GetDescription() - 0x514 < 100) {
             return ResultInvalidFileData;
         }
-        if (in_result.description - 0x5dc < 100) {
+        if (in_result.GetDescription() - 0x5dc < 100) {
             return ResultInvalidFileData;
         }
 
-        if (in_result.description - 0x578 < 100) {
+        if (in_result.GetDescription() - 0x578 < 100) {
             if (in_result == ResultFileCountLimit) {
                 return ResultUnknown22;
             }
@@ -244,9 +244,10 @@ Result IAlbumAccessorService::TranslateResult(Result in_result) {
         return ResultUnknown1024;
     }
 
-    if (in_result.module == ErrorModule::FS) {
-        if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
-            (((in_result.description - 3000) >> 3) < 0x271)) {
+    if (in_result.GetModule() == ErrorModule::FS) {
+        if ((in_result.GetDescription() >> 0xc < 0x7d) ||
+            (in_result.GetDescription() - 1000 < 2000) ||
+            (((in_result.GetDescription() - 3000) >> 3) < 0x271)) {
             // TODO: Translate FS error
             return in_result;
         }
diff --git a/src/core/hle/service/cmif_serialization.h b/src/core/hle/service/cmif_serialization.h
new file mode 100755
index 000000000..8e8cf2507
--- /dev/null
+++ b/src/core/hle/service/cmif_serialization.h
@@ -0,0 +1,337 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/div_ceil.h"
+
+#include "core/hle/service/cmif_types.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+
+// clang-format off
+struct RequestLayout {
+    u32 copy_handle_count;
+    u32 move_handle_count;
+    u32 cmif_raw_data_size;
+    u32 domain_interface_count;
+};
+
+template <ArgumentType Type1, ArgumentType Type2, typename MethodArguments, size_t PrevAlign = 1, size_t DataOffset = 0, size_t ArgIndex = 0>
+constexpr u32 GetArgumentRawDataSize() {
+    if constexpr (ArgIndex >= std::tuple_size_v<MethodArguments>) {
+        return static_cast<u32>(DataOffset);
+    } else {
+        using ArgType = std::tuple_element_t<ArgIndex, MethodArguments>;
+
+        if constexpr (ArgumentTraits<ArgType>::Type == Type1 || ArgumentTraits<ArgType>::Type == Type2) {
+            constexpr size_t ArgAlign = alignof(ArgType);
+            constexpr size_t ArgSize = sizeof(ArgType);
+
+            static_assert(PrevAlign <= ArgAlign, "Input argument is not ordered by alignment");
+
+            constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign);
+            constexpr size_t ArgEnd = ArgOffset + ArgSize;
+
+            return GetArgumentRawDataSize<Type1, Type2, MethodArguments, ArgAlign, ArgEnd, ArgIndex + 1>();
+        } else {
+            return GetArgumentRawDataSize<Type1, Type2, MethodArguments, PrevAlign, DataOffset, ArgIndex + 1>();
+        }
+    }
+}
+
+template <ArgumentType DataType, typename MethodArguments, size_t ArgCount = 0, size_t ArgIndex = 0>
+constexpr u32 GetArgumentTypeCount() {
+    if constexpr (ArgIndex >= std::tuple_size_v<MethodArguments>) {
+        return static_cast<u32>(ArgCount);
+    } else {
+        using ArgType = std::tuple_element_t<ArgIndex, MethodArguments>;
+
+        if constexpr (ArgumentTraits<ArgType>::Type == DataType) {
+            return GetArgumentTypeCount<DataType, MethodArguments, ArgCount + 1, ArgIndex + 1>();
+        } else {
+            return GetArgumentTypeCount<DataType, MethodArguments, ArgCount, ArgIndex + 1>();
+        }
+    }
+}
+
+template <typename MethodArguments>
+constexpr RequestLayout GetNonDomainReplyInLayout() {
+    return RequestLayout{
+        .copy_handle_count = GetArgumentTypeCount<ArgumentType::InCopyHandle, MethodArguments>(),
+        .move_handle_count = 0,
+        .cmif_raw_data_size = GetArgumentRawDataSize<ArgumentType::InData, ArgumentType::InProcessId, MethodArguments>(),
+        .domain_interface_count = 0,
+    };
+}
+
+template <typename MethodArguments>
+constexpr RequestLayout GetDomainReplyInLayout() {
+    return RequestLayout{
+        .copy_handle_count = GetArgumentTypeCount<ArgumentType::InCopyHandle, MethodArguments>(),
+        .move_handle_count = 0,
+        .cmif_raw_data_size = GetArgumentRawDataSize<ArgumentType::InData, ArgumentType::InProcessId, MethodArguments>(),
+        .domain_interface_count = GetArgumentTypeCount<ArgumentType::InInterface, MethodArguments>(),
+    };
+}
+
+template <typename MethodArguments>
+constexpr RequestLayout GetNonDomainReplyOutLayout() {
+    return RequestLayout{
+        .copy_handle_count = GetArgumentTypeCount<ArgumentType::OutCopyHandle, MethodArguments>(),
+        .move_handle_count = GetArgumentTypeCount<ArgumentType::OutMoveHandle, MethodArguments>() + GetArgumentTypeCount<ArgumentType::OutInterface, MethodArguments>(),
+        .cmif_raw_data_size = GetArgumentRawDataSize<ArgumentType::OutData, ArgumentType::OutData, MethodArguments>(),
+        .domain_interface_count = 0,
+    };
+}
+
+template <typename MethodArguments>
+constexpr RequestLayout GetDomainReplyOutLayout() {
+    return RequestLayout{
+        .copy_handle_count = GetArgumentTypeCount<ArgumentType::OutCopyHandle, MethodArguments>(),
+        .move_handle_count = GetArgumentTypeCount<ArgumentType::OutMoveHandle, MethodArguments>(),
+        .cmif_raw_data_size = GetArgumentRawDataSize<ArgumentType::OutData, ArgumentType::OutData, MethodArguments>(),
+        .domain_interface_count = GetArgumentTypeCount<ArgumentType::OutInterface, MethodArguments>(),
+    };
+}
+
+template <bool Domain, typename MethodArguments>
+constexpr RequestLayout GetReplyInLayout() {
+    return Domain ? GetDomainReplyInLayout<MethodArguments>() : GetNonDomainReplyInLayout<MethodArguments>();
+}
+
+template <bool Domain, typename MethodArguments>
+constexpr RequestLayout GetReplyOutLayout() {
+    return Domain ? GetDomainReplyOutLayout<MethodArguments>() : GetNonDomainReplyOutLayout<MethodArguments>();
+}
+
+using OutTemporaryBuffers = std::array<Common::ScratchBuffer<u8>, 3>;
+
+template <bool Domain, typename MethodArguments, typename CallArguments, size_t PrevAlign = 1, size_t DataOffset = 0, size_t HandleIndex = 0, size_t InBufferIndex = 0, size_t OutBufferIndex = 0, bool RawDataFinished = false, size_t ArgIndex = 0>
+void ReadInArgument(CallArguments& args, const u8* raw_data, HLERequestContext& ctx, OutTemporaryBuffers& temp) {
+    if constexpr (ArgIndex >= std::tuple_size_v<CallArguments>) {
+        return;
+    } else {
+        using ArgType = std::tuple_element_t<ArgIndex, MethodArguments>;
+
+        if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InData || ArgumentTraits<ArgType>::Type == ArgumentType::InProcessId) {
+            constexpr size_t ArgAlign = alignof(ArgType);
+            constexpr size_t ArgSize = sizeof(ArgType);
+
+            static_assert(PrevAlign <= ArgAlign, "Input argument is not ordered by alignment");
+            static_assert(!RawDataFinished, "All input interface arguments must appear after raw data");
+
+            constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign);
+            constexpr size_t ArgEnd = ArgOffset + ArgSize;
+
+            if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InProcessId) {
+                // TODO: abort parsing if PID is not provided?
+                // TODO: validate against raw data value?
+                std::get<ArgIndex>(args).pid = ctx.GetPID();
+            } else {
+                std::memcpy(&std::get<ArgIndex>(args), raw_data + ArgOffset, ArgSize);
+            }
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, ArgAlign, ArgEnd, HandleIndex, InBufferIndex, OutBufferIndex, false, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InInterface) {
+            constexpr size_t ArgAlign = alignof(u32);
+            constexpr size_t ArgSize = sizeof(u32);
+            constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign);
+            constexpr size_t ArgEnd = ArgOffset + ArgSize;
+
+            static_assert(Domain);
+            ASSERT(ctx.GetDomainMessageHeader().input_object_count > 0);
+
+            u32 value{};
+            std::memcpy(&value, raw_data + ArgOffset, ArgSize);
+            std::get<ArgIndex>(args) = ctx.GetDomainHandler<ArgType::Type>(value - 1);
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, ArgAlign, ArgEnd, HandleIndex, InBufferIndex, OutBufferIndex, true, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InCopyHandle) {
+            std::get<ArgIndex>(args) = std::move(ctx.GetObjectFromHandle<typename ArgType::Type>(ctx.GetCopyHandle(HandleIndex)));
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex + 1, InBufferIndex, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InLargeData) {
+            constexpr size_t BufferSize = sizeof(ArgType);
+
+            // Clear the existing data.
+            std::memset(&std::get<ArgIndex>(args), 0, BufferSize);
+
+            std::span<const u8> buffer{};
+
+            ASSERT(ctx.CanReadBuffer(InBufferIndex));
+            if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) {
+                buffer = ctx.ReadBuffer(InBufferIndex);
+            } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) {
+                buffer = ctx.ReadBufferA(InBufferIndex);
+            } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ {
+                buffer = ctx.ReadBufferX(InBufferIndex);
+            }
+
+            std::memcpy(&std::get<ArgIndex>(args), buffer.data(), std::min(BufferSize, buffer.size()));
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex, InBufferIndex + 1, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::InBuffer) {
+            using ElementType = typename ArgType::Type;
+
+            std::span<const u8> buffer{};
+
+            if (ctx.CanReadBuffer(InBufferIndex)) {
+                if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) {
+                    buffer = ctx.ReadBuffer(InBufferIndex);
+                } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) {
+                    buffer = ctx.ReadBufferA(InBufferIndex);
+                } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ {
+                    buffer = ctx.ReadBufferX(InBufferIndex);
+                }
+            }
+
+            ElementType* ptr = (ElementType*) buffer.data();
+            size_t size = buffer.size() / sizeof(ElementType);
+
+            std::get<ArgIndex>(args) = std::span(ptr, size);
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex, InBufferIndex + 1, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutLargeData) {
+            constexpr size_t BufferSize = sizeof(ArgType);
+
+            // Clear the existing data.
+            std::memset(&std::get<ArgIndex>(args), 0, BufferSize);
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex, InBufferIndex, OutBufferIndex + 1, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutBuffer) {
+            using ElementType = typename ArgType::Type;
+
+            // Set up scratch buffer.
+            auto& buffer = temp[OutBufferIndex];
+            if (ctx.CanWriteBuffer(OutBufferIndex)) {
+                buffer.resize_destructive(ctx.GetWriteBufferSize(OutBufferIndex));
+            } else {
+                buffer.resize_destructive(0);
+            }
+
+            ElementType* ptr = (ElementType*) buffer.data();
+            size_t size = buffer.size() / sizeof(ElementType);
+
+            std::get<ArgIndex>(args) = std::span(ptr, size);
+
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex, InBufferIndex, OutBufferIndex + 1, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else {
+            return ReadInArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, HandleIndex, InBufferIndex, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        }
+    }
+}
+
+template <bool Domain, typename MethodArguments, typename CallArguments, size_t PrevAlign = 1, size_t DataOffset = 0, size_t OutBufferIndex = 0, bool RawDataFinished = false, size_t ArgIndex = 0>
+void WriteOutArgument(CallArguments& args, u8* raw_data, HLERequestContext& ctx, OutTemporaryBuffers& temp) {
+    if constexpr (ArgIndex >= std::tuple_size_v<CallArguments>) {
+        return;
+    } else {
+        using ArgType = std::tuple_element_t<ArgIndex, MethodArguments>;
+
+        if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutData) {
+            constexpr size_t ArgAlign = alignof(ArgType);
+            constexpr size_t ArgSize = sizeof(ArgType);
+
+            static_assert(PrevAlign <= ArgAlign, "Output argument is not ordered by alignment");
+            static_assert(!RawDataFinished, "All output interface arguments must appear after raw data");
+
+            constexpr size_t ArgOffset = Common::AlignUp(DataOffset, ArgAlign);
+            constexpr size_t ArgEnd = ArgOffset + ArgSize;
+
+            std::memcpy(raw_data + ArgOffset, &std::get<ArgIndex>(args), ArgSize);
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, ArgAlign, ArgEnd, OutBufferIndex, false, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutInterface) {
+            if constexpr (Domain) {
+                ctx.AddDomainObject(std::get<ArgIndex>(args));
+            } else {
+                ctx.AddMoveInterface(std::get<ArgIndex>(args));
+            }
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex, true, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutCopyHandle) {
+            ctx.AddCopyObject(std::get<ArgIndex>(args).GetPointerUnsafe());
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutMoveHandle) {
+            ctx.AddMoveObject(std::get<ArgIndex>(args).GetPointerUnsafe());
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutLargeData) {
+            constexpr size_t BufferSize = sizeof(ArgType);
+
+            ASSERT(ctx.CanWriteBuffer(OutBufferIndex));
+            if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) {
+                ctx.WriteBuffer(std::get<ArgIndex>(args), OutBufferIndex);
+            } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) {
+                ctx.WriteBufferB(&std::get<ArgIndex>(args), BufferSize, OutBufferIndex);
+            } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ {
+                ctx.WriteBufferC(&std::get<ArgIndex>(args), BufferSize, OutBufferIndex);
+            }
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex + 1, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        } else if constexpr (ArgumentTraits<ArgType>::Type == ArgumentType::OutBuffer) {
+            auto& buffer = temp[OutBufferIndex];
+            const size_t size = buffer.size();
+
+            if (ctx.CanWriteBuffer(OutBufferIndex)) {
+                if constexpr (ArgType::Attr & BufferAttr_HipcAutoSelect) {
+                    ctx.WriteBuffer(buffer.data(), size, OutBufferIndex);
+                } else if constexpr (ArgType::Attr & BufferAttr_HipcMapAlias) {
+                    ctx.WriteBufferB(buffer.data(), size, OutBufferIndex);
+                } else /* if (ArgType::Attr & BufferAttr_HipcPointer) */ {
+                    ctx.WriteBufferC(buffer.data(), size, OutBufferIndex);
+                }
+            }
+
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex + 1, RawDataFinished, ArgIndex + 1>( args, raw_data, ctx, temp);
+        } else {
+            return WriteOutArgument<Domain, MethodArguments, CallArguments, PrevAlign, DataOffset, OutBufferIndex, RawDataFinished, ArgIndex + 1>(args, raw_data, ctx, temp);
+        }
+    }
+}
+
+template <bool Domain, typename T, typename... A>
+void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) {
+    // Verify domain state.
+    if constexpr (Domain) {
+        ASSERT_MSG(ctx.GetManager()->IsDomain(), "Domain reply used on non-domain session");
+    } else {
+        ASSERT_MSG(!ctx.GetManager()->IsDomain(), "Non-domain reply used on domain session");
+    }
+
+    using MethodArguments = std::tuple<std::remove_reference_t<A>...>;
+
+    OutTemporaryBuffers buffers{};
+    auto call_arguments = std::tuple<typename RemoveOut<A>::Type...>();
+
+    // Read inputs.
+    const size_t offset_plus_command_id = ctx.GetDataPayloadOffset() + 2;
+    ReadInArgument<Domain, MethodArguments>(call_arguments, reinterpret_cast<u8*>(ctx.CommandBuffer() + offset_plus_command_id), ctx, buffers);
+
+    // Call.
+    const auto Callable = [&]<typename... CallArgs>(CallArgs&... args) {
+        return (t.*f)(args...);
+    };
+    const Result res = std::apply(Callable, call_arguments);
+
+    // Write result.
+    constexpr RequestLayout layout = GetReplyOutLayout<Domain, MethodArguments>();
+    IPC::ResponseBuilder rb{ctx, 2 + Common::DivCeil(layout.cmif_raw_data_size, sizeof(u32)), layout.copy_handle_count, layout.move_handle_count + layout.domain_interface_count};
+    rb.Push(res);
+
+    // Write out arguments.
+    WriteOutArgument<Domain, MethodArguments>(call_arguments, reinterpret_cast<u8*>(ctx.CommandBuffer() + rb.GetCurrentOffset()), ctx, buffers);
+}
+// clang-format on
+
+template <typename Self>
+template <bool Domain, auto F>
+inline void ServiceFramework<Self>::CmifReplyWrap(HLERequestContext& ctx) {
+    return CmifReplyWrapImpl<Domain>(ctx, *static_cast<Self*>(this), F);
+}
+
+} // namespace Service
diff --git a/src/core/hle/service/cmif_types.h b/src/core/hle/service/cmif_types.h
new file mode 100755
index 000000000..b80028c19
--- /dev/null
+++ b/src/core/hle/service/cmif_types.h
@@ -0,0 +1,234 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/hle_ipc.h"
+
+namespace Service {
+
+// clang-format off
+template <typename T>
+class Out {
+public:
+    /* implicit */ Out(T& t) : raw(&t) {}
+    ~Out() = default;
+
+    T* Get() const {
+        return raw;
+    }
+
+    T& operator*() {
+        return *raw;
+    }
+
+private:
+    T* raw;
+};
+
+template <typename T>
+using SharedPointer = std::shared_ptr<T>;
+
+struct ClientProcessId {
+    explicit operator bool() const {
+        return pid != 0;
+    }
+
+    const u64& operator*() const {
+        return pid;
+    }
+
+    u64 pid;
+};
+
+using ClientAppletResourceUserId = ClientProcessId;
+
+template <typename T>
+class InCopyHandle : public Kernel::KScopedAutoObject<T> {
+public:
+    using Type = T;
+
+    template <typename... Args>
+    /* implicit */ InCopyHandle(Args&&... args) : Kernel::KScopedAutoObject<T>(std::forward<Args...>(args)...) {}
+    ~InCopyHandle() = default;
+
+    InCopyHandle& operator=(InCopyHandle&& rhs) {
+        Kernel::KScopedAutoObject<T>::operator=(std::move(rhs));
+        return *this;
+    }
+};
+
+template <typename T>
+class OutCopyHandle : public Kernel::KScopedAutoObject<T> {
+public:
+    using Type = T;
+
+    template <typename... Args>
+    /* implicit */ OutCopyHandle(Args&&... args) : Kernel::KScopedAutoObject<T>(std::forward<Args...>(args)...) {}
+    ~OutCopyHandle() = default;
+
+    OutCopyHandle& operator=(OutCopyHandle&& rhs) {
+        Kernel::KScopedAutoObject<T>::operator=(std::move(rhs));
+        return *this;
+    }
+};
+
+template <typename T>
+class OutMoveHandle : public Kernel::KScopedAutoObject<T> {
+public:
+    using Type = T;
+
+    template <typename... Args>
+    /* implicit */ OutMoveHandle(Args&&... args) : Kernel::KScopedAutoObject<T>(std::forward<Args...>(args)...) {}
+    ~OutMoveHandle() = default;
+
+    OutMoveHandle& operator=(OutMoveHandle&& rhs) {
+        Kernel::KScopedAutoObject<T>::operator=(std::move(rhs));
+        return *this;
+    }
+};
+
+enum BufferAttr : int {
+    BufferAttr_In = (1U << 0),
+    BufferAttr_Out = (1U << 1),
+    BufferAttr_HipcMapAlias = (1U << 2),
+    BufferAttr_HipcPointer = (1U << 3),
+    BufferAttr_FixedSize = (1U << 4),
+    BufferAttr_HipcAutoSelect = (1U << 5),
+    BufferAttr_HipcMapTransferAllowsNonSecure = (1U << 6),
+    BufferAttr_HipcMapTransferAllowsNonDevice = (1U << 7),
+};
+
+template <typename T, int A>
+struct Buffer : public std::span<T> {
+    static_assert(std::is_trivial_v<T>, "Buffer type must be trivial");
+    static_assert((A & BufferAttr_FixedSize) == 0, "Buffer attr must not contain FixedSize");
+    static_assert(((A & BufferAttr_In) == 0) ^ ((A & BufferAttr_Out) == 0), "Buffer attr must be In or Out");
+    static constexpr BufferAttr Attr = static_cast<BufferAttr>(A);
+    using Type = T;
+
+    Buffer& operator=(const std::span<T>& rhs) {
+        std::span<T>::operator=(rhs);
+        return *this;
+    }
+
+    T& operator*() const {
+        return *this->data();
+    }
+
+    explicit operator bool() const {
+        return this->size() > 0;
+    }
+};
+
+template <BufferAttr A>
+using InBuffer = Buffer<const u8, BufferAttr_In | A>;
+
+template <typename T, BufferAttr A>
+using InArray = Buffer<T, BufferAttr_In | A>;
+
+template <BufferAttr A>
+using OutBuffer = Buffer<u8, BufferAttr_Out | A>;
+
+template <typename T, BufferAttr A>
+using OutArray = Buffer<T, BufferAttr_Out | A>;
+
+template <typename T, int A>
+struct LargeData : public T {
+    static_assert(std::is_trivial_v<T>, "LargeData type must be trivial");
+    static_assert((A & BufferAttr_FixedSize) != 0, "LargeData attr must contain FixedSize");
+    static_assert(((A & BufferAttr_In) == 0) ^ ((A & BufferAttr_Out) == 0), "LargeData attr must be In or Out");
+    static constexpr BufferAttr Attr = static_cast<BufferAttr>(A);
+    using Type = T;
+};
+
+template <typename T, BufferAttr A>
+using InLargeData = LargeData<T, BufferAttr_FixedSize | BufferAttr_In | A>;
+
+template <typename T, BufferAttr A>
+using OutLargeData = LargeData<T, BufferAttr_FixedSize | BufferAttr_Out | A>;
+
+template <typename T>
+struct RemoveOut {
+    using Type = std::remove_reference_t<T>;
+};
+
+template <typename T>
+struct RemoveOut<Out<T>> {
+    using Type = T;
+};
+
+enum class ArgumentType {
+    InProcessId,
+    InData,
+    InInterface,
+    InCopyHandle,
+    OutData,
+    OutInterface,
+    OutCopyHandle,
+    OutMoveHandle,
+    InBuffer,
+    InLargeData,
+    OutBuffer,
+    OutLargeData,
+};
+
+template <typename T>
+struct ArgumentTraits;
+
+template <>
+struct ArgumentTraits<ClientProcessId> {
+    static constexpr ArgumentType Type = ArgumentType::InProcessId;
+};
+
+template <typename T>
+struct ArgumentTraits<SharedPointer<T>> {
+    static constexpr ArgumentType Type = ArgumentType::InInterface;
+};
+
+template <typename T>
+struct ArgumentTraits<InCopyHandle<T>> {
+    static constexpr ArgumentType Type = ArgumentType::InCopyHandle;
+};
+
+template <typename T>
+struct ArgumentTraits<Out<SharedPointer<T>>> {
+    static constexpr ArgumentType Type = ArgumentType::OutInterface;
+};
+
+template <typename T>
+struct ArgumentTraits<Out<T>> {
+    static constexpr ArgumentType Type = ArgumentType::OutData;
+};
+
+template <typename T>
+struct ArgumentTraits<OutCopyHandle<T>> {
+    static constexpr ArgumentType Type = ArgumentType::OutCopyHandle;
+};
+
+template <typename T>
+struct ArgumentTraits<OutMoveHandle<T>> {
+    static constexpr ArgumentType Type = ArgumentType::OutMoveHandle;
+};
+
+template <typename T, int A>
+struct ArgumentTraits<Buffer<T, A>> {
+    static constexpr ArgumentType Type = (A & BufferAttr_In) == 0 ? ArgumentType::OutBuffer : ArgumentType::InBuffer;
+};
+
+template <typename T, int A>
+struct ArgumentTraits<LargeData<T, A>> {
+    static constexpr ArgumentType Type = (A & BufferAttr_In) == 0 ? ArgumentType::OutLargeData : ArgumentType::InLargeData;
+};
+
+template <typename T>
+struct ArgumentTraits {
+    static constexpr ArgumentType Type = ArgumentType::InData;
+};
+// clang-format on
+
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index f545456ca..19376d266 100755
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -73,8 +73,8 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F
         "Program entry point:             0x{:16X}\n"
         "\n",
         Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
-        2000 + static_cast<u32>(error_code.module.Value()),
-        static_cast<u32>(error_code.description.Value()), info.set_flags, info.program_entry_point);
+        2000 + static_cast<u32>(error_code.GetModule()),
+        static_cast<u32>(error_code.GetDescription()), info.set_flags, info.program_entry_point);
     if (info.backtrace_size != 0x0) {
         crash_report += "Registers:\n";
         for (size_t i = 0; i < info.registers.size(); i++) {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index f78fbe669..322957e5e 100755
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -12,18 +12,17 @@
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/errors.h"
-#include "core/file_sys/mode.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs_factory.h"
 #include "core/file_sys/savedata_factory.h"
 #include "core/file_sys/sdmc_factory.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/hle/service/filesystem/filesystem.h"
-#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/fsp/fsp_ldr.h"
+#include "core/hle/service/filesystem/fsp/fsp_pr.h"
+#include "core/hle/service/filesystem/fsp/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"
@@ -53,12 +52,12 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size
     std::string path(Common::FS::SanitizePath(path_));
     auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
     if (dir == nullptr) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
 
-    FileSys::EntryType entry_type{};
+    FileSys::DirectoryEntryType entry_type{};
     if (GetEntryType(&entry_type, path) == ResultSuccess) {
-        return FileSys::ERROR_PATH_ALREADY_EXISTS;
+        return FileSys::ResultPathAlreadyExists;
     }
 
     auto file = dir->CreateFile(Common::FS::GetFilename(path));
@@ -82,7 +81,7 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
 
     auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
     if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
     if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
         // TODO(DarkLordZach): Find a better error code for this
@@ -153,12 +152,12 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
     if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
         // Use more-optimized vfs implementation rename.
         if (src == nullptr) {
-            return FileSys::ERROR_PATH_NOT_FOUND;
+            return FileSys::ResultPathNotFound;
         }
 
         if (dst && Common::FS::Exists(dst->GetFullPath())) {
             LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath());
-            return FileSys::ERROR_PATH_ALREADY_EXISTS;
+            return FileSys::ResultPathAlreadyExists;
         }
 
         if (!src->Rename(Common::FS::GetFilename(dest_path))) {
@@ -195,7 +194,7 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
     if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
         // Use more-optimized vfs implementation rename.
         if (src == nullptr)
-            return FileSys::ERROR_PATH_NOT_FOUND;
+            return FileSys::ResultPathNotFound;
         if (!src->Rename(Common::FS::GetFilename(dest_path))) {
             // TODO(DarkLordZach): Find a better error code for this
             return ResultUnknown;
@@ -214,7 +213,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
 }
 
 Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
-                                            const std::string& path_, FileSys::Mode mode) const {
+                                            const std::string& path_,
+                                            FileSys::OpenMode mode) const {
     const std::string path(Common::FS::SanitizePath(path_));
     std::string_view npath = path;
     while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
@@ -223,10 +223,10 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
 
     auto file = backing->GetFileRelative(npath);
     if (file == nullptr) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
 
-    if (mode == FileSys::Mode::Append) {
+    if (mode == FileSys::OpenMode::AllowAppend) {
         *out_file = std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize());
     } else {
         *out_file = file;
@@ -241,50 +241,50 @@ Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_direct
     auto dir = GetDirectoryRelativeWrapped(backing, path);
     if (dir == nullptr) {
         // TODO(DarkLordZach): Find a better error code for this
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
     *out_directory = dir;
     return ResultSuccess;
 }
 
-Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type,
+Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::DirectoryEntryType* out_entry_type,
                                                 const std::string& path_) const {
     std::string path(Common::FS::SanitizePath(path_));
     auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
     if (dir == nullptr) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
 
     auto filename = Common::FS::GetFilename(path);
     // TODO(Subv): Some games use the '/' path, find out what this means.
     if (filename.empty()) {
-        *out_entry_type = FileSys::EntryType::Directory;
+        *out_entry_type = FileSys::DirectoryEntryType::Directory;
         return ResultSuccess;
     }
 
     if (dir->GetFile(filename) != nullptr) {
-        *out_entry_type = FileSys::EntryType::File;
+        *out_entry_type = FileSys::DirectoryEntryType::File;
         return ResultSuccess;
     }
 
     if (dir->GetSubdirectory(filename) != nullptr) {
-        *out_entry_type = FileSys::EntryType::Directory;
+        *out_entry_type = FileSys::DirectoryEntryType::Directory;
         return ResultSuccess;
     }
 
-    return FileSys::ERROR_PATH_NOT_FOUND;
+    return FileSys::ResultPathNotFound;
 }
 
 Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
     FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const {
     auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
     if (dir == nullptr) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
 
-    FileSys::EntryType entry_type;
+    FileSys::DirectoryEntryType entry_type;
     if (GetEntryType(&entry_type, path) != ResultSuccess) {
-        return FileSys::ERROR_PATH_NOT_FOUND;
+        return FileSys::ResultPathNotFound;
     }
 
     *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path));
@@ -317,7 +317,7 @@ Result FileSystemController::OpenProcess(
 
     const auto it = registrations.find(process_id);
     if (it == registrations.end()) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     *out_program_id = it->second.program_id;
@@ -347,7 +347,7 @@ std::shared_ptr<SaveDataController> FileSystemController::OpenSaveDataController
 std::shared_ptr<FileSys::SaveDataFactory> FileSystemController::CreateSaveDataFactory(
     ProgramId program_id) {
     using YuzuPath = Common::FS::YuzuPath;
-    const auto rw_mode = FileSys::Mode::ReadWrite;
+    const auto rw_mode = FileSys::OpenMode::ReadWrite;
 
     auto vfs = system.GetFilesystem();
     const auto nand_directory =
@@ -360,12 +360,12 @@ Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const {
     LOG_TRACE(Service_FS, "Opening SDMC");
 
     if (sdmc_factory == nullptr) {
-        return FileSys::ERROR_SD_CARD_NOT_FOUND;
+        return FileSys::ResultPortSdCardNoDevice;
     }
 
     auto sdmc = sdmc_factory->Open();
     if (sdmc == nullptr) {
-        return FileSys::ERROR_SD_CARD_NOT_FOUND;
+        return FileSys::ResultPortSdCardNoDevice;
     }
 
     *out_sdmc = sdmc;
@@ -377,12 +377,12 @@ Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_parti
     LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id);
 
     if (bis_factory == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     auto part = bis_factory->OpenPartition(id);
     if (part == nullptr) {
-        return FileSys::ERROR_INVALID_ARGUMENT;
+        return FileSys::ResultInvalidArgument;
     }
 
     *out_bis_partition = part;
@@ -394,12 +394,12 @@ Result FileSystemController::OpenBISPartitionStorage(
     LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id);
 
     if (bis_factory == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem());
     if (part == nullptr) {
-        return FileSys::ERROR_INVALID_ARGUMENT;
+        return FileSys::ResultInvalidArgument;
     }
 
     *out_bis_partition_storage = part;
@@ -686,15 +686,15 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
     using YuzuPath = Common::FS::YuzuPath;
     const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir);
     const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
-    const auto rw_mode = FileSys::Mode::ReadWrite;
+    const auto rw_mode = FileSys::OpenMode::ReadWrite;
 
     auto nand_directory =
         vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
     auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);
-    auto load_directory =
-        vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read);
-    auto sd_load_directory =
-        vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read);
+    auto load_directory = vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir),
+                                            FileSys::OpenMode::Read);
+    auto sd_load_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path),
+                                               FileSys::OpenMode::Read);
     auto dump_directory =
         vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);
 
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 6b1ba2b49..c7fd5e2db 100755
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -4,9 +4,11 @@
 #pragma once
 
 #include <memory>
+#include <mutex>
 #include "common/common_types.h"
-#include "core/file_sys/directory.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/fs_directory.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/result.h"
 
 namespace Core {
@@ -26,7 +28,6 @@ class XCI;
 
 enum class BisPartitionId : u32;
 enum class ContentRecordType : u8;
-enum class Mode : u32;
 enum class SaveDataSpaceId : u8;
 enum class SaveDataType : u8;
 enum class StorageId : u8;
@@ -57,13 +58,6 @@ enum class ImageDirectoryId : u32 {
     SdCard,
 };
 
-enum class OpenDirectoryMode : u64 {
-    Directory = (1 << 0),
-    File = (1 << 1),
-    All = Directory | File
-};
-DECLARE_ENUM_FLAG_OPERATORS(OpenDirectoryMode);
-
 using ProcessId = u64;
 using ProgramId = u64;
 
@@ -237,7 +231,7 @@ public:
      * @return Opened file, or error code
      */
     Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path,
-                    FileSys::Mode mode) const;
+                    FileSys::OpenMode mode) const;
 
     /**
      * Open a directory specified by its path
@@ -250,7 +244,7 @@ public:
      * Get the type of the specified path
      * @return The type of the specified path or error code
      */
-    Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const;
+    Result GetEntryType(FileSys::DirectoryEntryType* out_entry_type, const std::string& path) const;
 
     /**
      * Get the timestamp of the specified path
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
new file mode 100755
index 000000000..39690018b
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/hle/service/filesystem/fsp/fs_i_directory.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::FileSystem {
+
+template <typename T>
+static void BuildEntryIndex(std::vector<FileSys::DirectoryEntry>& entries,
+                            const std::vector<T>& new_data, FileSys::DirectoryEntryType type) {
+    entries.reserve(entries.size() + new_data.size());
+
+    for (const auto& new_entry : new_data) {
+        auto name = new_entry->GetName();
+
+        if (type == FileSys::DirectoryEntryType::File &&
+            name == FileSys::GetSaveDataSizeFileName()) {
+            continue;
+        }
+
+        entries.emplace_back(name, static_cast<s8>(type),
+                             type == FileSys::DirectoryEntryType::Directory ? 0
+                                                                            : new_entry->GetSize());
+    }
+}
+
+IDirectory::IDirectory(Core::System& system_, FileSys::VirtualDir backend_,
+                       FileSys::OpenDirectoryMode mode)
+    : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) {
+    static const FunctionInfo functions[] = {
+        {0, &IDirectory::Read, "Read"},
+        {1, &IDirectory::GetEntryCount, "GetEntryCount"},
+    };
+    RegisterHandlers(functions);
+
+    // TODO(DarkLordZach): Verify that this is the correct behavior.
+    // Build entry index now to save time later.
+    if (True(mode & FileSys::OpenDirectoryMode::Directory)) {
+        BuildEntryIndex(entries, backend->GetSubdirectories(),
+                        FileSys::DirectoryEntryType::Directory);
+    }
+    if (True(mode & FileSys::OpenDirectoryMode::File)) {
+        BuildEntryIndex(entries, backend->GetFiles(), FileSys::DirectoryEntryType::File);
+    }
+}
+
+void IDirectory::Read(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called.");
+
+    // Calculate how many entries we can fit in the output buffer
+    const u64 count_entries = ctx.GetWriteBufferNumElements<FileSys::DirectoryEntry>();
+
+    // Cap at total number of entries.
+    const u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index);
+
+    // Determine data start and end
+    const auto* begin = reinterpret_cast<u8*>(entries.data() + next_entry_index);
+    const auto* end = reinterpret_cast<u8*>(entries.data() + next_entry_index + actual_entries);
+    const auto range_size = static_cast<std::size_t>(std::distance(begin, end));
+
+    next_entry_index += actual_entries;
+
+    // Write the data to memory
+    ctx.WriteBuffer(begin, range_size);
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push(actual_entries);
+}
+
+void IDirectory::GetEntryCount(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    u64 count = entries.size() - next_entry_index;
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push(count);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.h b/src/core/hle/service/filesystem/fsp/fs_i_directory.h
new file mode 100755
index 000000000..793ecfcd7
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.h
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/service.h"
+
+namespace FileSys {
+struct DirectoryEntry;
+}
+
+namespace Service::FileSystem {
+
+class IDirectory final : public ServiceFramework<IDirectory> {
+public:
+    explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_,
+                        FileSys::OpenDirectoryMode mode);
+
+private:
+    FileSys::VirtualDir backend;
+    std::vector<FileSys::DirectoryEntry> entries;
+    u64 next_entry_index = 0;
+
+    void Read(HLERequestContext& ctx);
+    void GetEntryCount(HLERequestContext& ctx);
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.cpp b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp
new file mode 100755
index 000000000..9a18f6ec5
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp
@@ -0,0 +1,127 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/hle/service/filesystem/fsp/fs_i_file.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::FileSystem {
+
+IFile::IFile(Core::System& system_, FileSys::VirtualFile backend_)
+    : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) {
+    static const FunctionInfo functions[] = {
+        {0, &IFile::Read, "Read"},
+        {1, &IFile::Write, "Write"},
+        {2, &IFile::Flush, "Flush"},
+        {3, &IFile::SetSize, "SetSize"},
+        {4, &IFile::GetSize, "GetSize"},
+        {5, nullptr, "OperateRange"},
+        {6, nullptr, "OperateRangeWithBuffer"},
+    };
+    RegisterHandlers(functions);
+}
+
+void IFile::Read(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const u64 option = rp.Pop<u64>();
+    const s64 offset = rp.Pop<s64>();
+    const s64 length = rp.Pop<s64>();
+
+    LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, length);
+
+    // Error checking
+    if (length < 0) {
+        LOG_ERROR(Service_FS, "Length is less than 0, length={}", length);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidSize);
+        return;
+    }
+    if (offset < 0) {
+        LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidOffset);
+        return;
+    }
+
+    // Read the data from the Storage backend
+    std::vector<u8> output = backend->ReadBytes(length, offset);
+
+    // Write the data to memory
+    ctx.WriteBuffer(output);
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push(static_cast<u64>(output.size()));
+}
+
+void IFile::Write(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const u64 option = rp.Pop<u64>();
+    const s64 offset = rp.Pop<s64>();
+    const s64 length = rp.Pop<s64>();
+
+    LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset, length);
+
+    // Error checking
+    if (length < 0) {
+        LOG_ERROR(Service_FS, "Length is less than 0, length={}", length);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidSize);
+        return;
+    }
+    if (offset < 0) {
+        LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidOffset);
+        return;
+    }
+
+    const auto data = ctx.ReadBuffer();
+
+    ASSERT_MSG(static_cast<s64>(data.size()) <= length,
+               "Attempting to write more data than requested (requested={:016X}, actual={:016X}).",
+               length, data.size());
+
+    // Write the data to the Storage backend
+    const auto write_size =
+        static_cast<std::size_t>(std::distance(data.begin(), data.begin() + length));
+    const std::size_t written = backend->Write(data.data(), write_size, offset);
+
+    ASSERT_MSG(static_cast<s64>(written) == length,
+               "Could not write all bytes to file (requested={:016X}, actual={:016X}).", length,
+               written);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IFile::Flush(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    // Exists for SDK compatibiltity -- No need to flush file.
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IFile::SetSize(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const u64 size = rp.Pop<u64>();
+    LOG_DEBUG(Service_FS, "called, size={}", size);
+
+    backend->Resize(size);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IFile::GetSize(HLERequestContext& ctx) {
+    const u64 size = backend->GetSize();
+    LOG_DEBUG(Service_FS, "called, size={}", size);
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push<u64>(size);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.h b/src/core/hle/service/filesystem/fsp/fs_i_file.h
new file mode 100755
index 000000000..5e5430c67
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_file.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/service.h"
+
+namespace Service::FileSystem {
+
+class IFile final : public ServiceFramework<IFile> {
+public:
+    explicit IFile(Core::System& system_, FileSys::VirtualFile backend_);
+
+private:
+    FileSys::VirtualFile backend;
+
+    void Read(HLERequestContext& ctx);
+    void Write(HLERequestContext& ctx);
+    void Flush(HLERequestContext& ctx);
+    void SetSize(HLERequestContext& ctx);
+    void GetSize(HLERequestContext& ctx);
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp
new file mode 100755
index 000000000..efa394dd1
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp
@@ -0,0 +1,262 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/string_util.h"
+#include "core/hle/service/filesystem/fsp/fs_i_directory.h"
+#include "core/hle/service/filesystem/fsp/fs_i_file.h"
+#include "core/hle/service/filesystem/fsp/fs_i_filesystem.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::FileSystem {
+
+IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_)
+    : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move(
+                                                                                  size_)} {
+    static const FunctionInfo functions[] = {
+        {0, &IFileSystem::CreateFile, "CreateFile"},
+        {1, &IFileSystem::DeleteFile, "DeleteFile"},
+        {2, &IFileSystem::CreateDirectory, "CreateDirectory"},
+        {3, &IFileSystem::DeleteDirectory, "DeleteDirectory"},
+        {4, &IFileSystem::DeleteDirectoryRecursively, "DeleteDirectoryRecursively"},
+        {5, &IFileSystem::RenameFile, "RenameFile"},
+        {6, nullptr, "RenameDirectory"},
+        {7, &IFileSystem::GetEntryType, "GetEntryType"},
+        {8, &IFileSystem::OpenFile, "OpenFile"},
+        {9, &IFileSystem::OpenDirectory, "OpenDirectory"},
+        {10, &IFileSystem::Commit, "Commit"},
+        {11, &IFileSystem::GetFreeSpaceSize, "GetFreeSpaceSize"},
+        {12, &IFileSystem::GetTotalSpaceSize, "GetTotalSpaceSize"},
+        {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
+        {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"},
+        {15, nullptr, "QueryEntry"},
+        {16, &IFileSystem::GetFileSystemAttribute, "GetFileSystemAttribute"},
+    };
+    RegisterHandlers(functions);
+}
+
+void IFileSystem::CreateFile(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    const u64 file_mode = rp.Pop<u64>();
+    const u32 file_size = rp.Pop<u32>();
+
+    LOG_DEBUG(Service_FS, "called. file={}, mode=0x{:X}, size=0x{:08X}", name, file_mode,
+              file_size);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.CreateFile(name, file_size));
+}
+
+void IFileSystem::DeleteFile(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. file={}", name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.DeleteFile(name));
+}
+
+void IFileSystem::CreateDirectory(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. directory={}", name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.CreateDirectory(name));
+}
+
+void IFileSystem::DeleteDirectory(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. directory={}", name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.DeleteDirectory(name));
+}
+
+void IFileSystem::DeleteDirectoryRecursively(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. directory={}", name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.DeleteDirectoryRecursively(name));
+}
+
+void IFileSystem::CleanDirectoryRecursively(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. Directory: {}", name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.CleanDirectoryRecursively(name));
+}
+
+void IFileSystem::RenameFile(HLERequestContext& ctx) {
+    const std::string src_name = Common::StringFromBuffer(ctx.ReadBuffer(0));
+    const std::string dst_name = Common::StringFromBuffer(ctx.ReadBuffer(1));
+
+    LOG_DEBUG(Service_FS, "called. file '{}' to file '{}'", src_name, dst_name);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(backend.RenameFile(src_name, dst_name));
+}
+
+void IFileSystem::OpenFile(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    const auto mode = static_cast<FileSys::OpenMode>(rp.Pop<u32>());
+
+    LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode);
+
+    FileSys::VirtualFile vfs_file{};
+    auto result = backend.OpenFile(&vfs_file, name, mode);
+    if (result != ResultSuccess) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(result);
+        return;
+    }
+
+    auto file = std::make_shared<IFile>(system, vfs_file);
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IFile>(std::move(file));
+}
+
+void IFileSystem::OpenDirectory(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+    const auto mode = rp.PopRaw<FileSys::OpenDirectoryMode>();
+
+    LOG_DEBUG(Service_FS, "called. directory={}, mode={}", name, mode);
+
+    FileSys::VirtualDir vfs_dir{};
+    auto result = backend.OpenDirectory(&vfs_dir, name);
+    if (result != ResultSuccess) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(result);
+        return;
+    }
+
+    auto directory = std::make_shared<IDirectory>(system, vfs_dir, mode);
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IDirectory>(std::move(directory));
+}
+
+void IFileSystem::GetEntryType(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_DEBUG(Service_FS, "called. file={}", name);
+
+    FileSys::DirectoryEntryType vfs_entry_type{};
+    auto result = backend.GetEntryType(&vfs_entry_type, name);
+    if (result != ResultSuccess) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(result);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push<u32>(static_cast<u32>(vfs_entry_type));
+}
+
+void IFileSystem::Commit(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called");
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IFileSystem::GetFreeSpaceSize(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push(size.get_free_size());
+}
+
+void IFileSystem::GetTotalSpaceSize(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push(size.get_total_size());
+}
+
+void IFileSystem::GetFileTimeStampRaw(HLERequestContext& ctx) {
+    const auto file_buffer = ctx.ReadBuffer();
+    const std::string name = Common::StringFromBuffer(file_buffer);
+
+    LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name);
+
+    FileSys::FileTimeStampRaw vfs_timestamp{};
+    auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name);
+    if (result != ResultSuccess) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(result);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 10};
+    rb.Push(ResultSuccess);
+    rb.PushRaw(vfs_timestamp);
+}
+
+void IFileSystem::GetFileSystemAttribute(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called");
+
+    struct FileSystemAttribute {
+        u8 dir_entry_name_length_max_defined;
+        u8 file_entry_name_length_max_defined;
+        u8 dir_path_name_length_max_defined;
+        u8 file_path_name_length_max_defined;
+        INSERT_PADDING_BYTES_NOINIT(0x5);
+        u8 utf16_dir_entry_name_length_max_defined;
+        u8 utf16_file_entry_name_length_max_defined;
+        u8 utf16_dir_path_name_length_max_defined;
+        u8 utf16_file_path_name_length_max_defined;
+        INSERT_PADDING_BYTES_NOINIT(0x18);
+        s32 dir_entry_name_length_max;
+        s32 file_entry_name_length_max;
+        s32 dir_path_name_length_max;
+        s32 file_path_name_length_max;
+        INSERT_PADDING_WORDS_NOINIT(0x5);
+        s32 utf16_dir_entry_name_length_max;
+        s32 utf16_file_entry_name_length_max;
+        s32 utf16_dir_path_name_length_max;
+        s32 utf16_file_path_name_length_max;
+        INSERT_PADDING_WORDS_NOINIT(0x18);
+        INSERT_PADDING_WORDS_NOINIT(0x1);
+    };
+    static_assert(sizeof(FileSystemAttribute) == 0xc0, "FileSystemAttribute has incorrect size");
+
+    FileSystemAttribute savedata_attribute{};
+    savedata_attribute.dir_entry_name_length_max_defined = true;
+    savedata_attribute.file_entry_name_length_max_defined = true;
+    savedata_attribute.dir_entry_name_length_max = 0x40;
+    savedata_attribute.file_entry_name_length_max = 0x40;
+
+    IPC::ResponseBuilder rb{ctx, 50};
+    rb.Push(ResultSuccess);
+    rb.PushRaw(savedata_attribute);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h
new file mode 100755
index 000000000..b06b3ef0e
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/fsp/fsp_util.h"
+#include "core/hle/service/service.h"
+
+namespace Service::FileSystem {
+
+class IFileSystem final : public ServiceFramework<IFileSystem> {
+public:
+    explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_);
+
+    void CreateFile(HLERequestContext& ctx);
+    void DeleteFile(HLERequestContext& ctx);
+    void CreateDirectory(HLERequestContext& ctx);
+    void DeleteDirectory(HLERequestContext& ctx);
+    void DeleteDirectoryRecursively(HLERequestContext& ctx);
+    void CleanDirectoryRecursively(HLERequestContext& ctx);
+    void RenameFile(HLERequestContext& ctx);
+    void OpenFile(HLERequestContext& ctx);
+    void OpenDirectory(HLERequestContext& ctx);
+    void GetEntryType(HLERequestContext& ctx);
+    void Commit(HLERequestContext& ctx);
+    void GetFreeSpaceSize(HLERequestContext& ctx);
+    void GetTotalSpaceSize(HLERequestContext& ctx);
+    void GetFileTimeStampRaw(HLERequestContext& ctx);
+    void GetFileSystemAttribute(HLERequestContext& ctx);
+
+private:
+    VfsDirectoryServiceWrapper backend;
+    SizeGetter size;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp
new file mode 100755
index 000000000..98223c1f9
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/errors.h"
+#include "core/hle/service/filesystem/fsp/fs_i_storage.h"
+#include "core/hle/service/ipc_helpers.h"
+
+namespace Service::FileSystem {
+
+IStorage::IStorage(Core::System& system_, FileSys::VirtualFile backend_)
+    : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) {
+    static const FunctionInfo functions[] = {
+        {0, &IStorage::Read, "Read"},
+        {1, nullptr, "Write"},
+        {2, nullptr, "Flush"},
+        {3, nullptr, "SetSize"},
+        {4, &IStorage::GetSize, "GetSize"},
+        {5, nullptr, "OperateRange"},
+    };
+    RegisterHandlers(functions);
+}
+
+void IStorage::Read(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const s64 offset = rp.Pop<s64>();
+    const s64 length = rp.Pop<s64>();
+
+    LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length);
+
+    // Error checking
+    if (length < 0) {
+        LOG_ERROR(Service_FS, "Length is less than 0, length={}", length);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidSize);
+        return;
+    }
+    if (offset < 0) {
+        LOG_ERROR(Service_FS, "Offset is less than 0, offset={}", offset);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(FileSys::ResultInvalidOffset);
+        return;
+    }
+
+    // Read the data from the Storage backend
+    std::vector<u8> output = backend->ReadBytes(length, offset);
+    // Write the data to memory
+    ctx.WriteBuffer(output);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IStorage::GetSize(HLERequestContext& ctx) {
+    const u64 size = backend->GetSize();
+    LOG_DEBUG(Service_FS, "called, size={}", size);
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push<u64>(size);
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.h b/src/core/hle/service/filesystem/fsp/fs_i_storage.h
new file mode 100755
index 000000000..cb5bebcc9
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/service.h"
+
+namespace Service::FileSystem {
+
+class IStorage final : public ServiceFramework<IStorage> {
+public:
+    explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_);
+
+private:
+    FileSys::VirtualFile backend;
+
+    void Read(HLERequestContext& ctx);
+    void GetSize(HLERequestContext& ctx);
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp
new file mode 100755
index 000000000..8ee733f47
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/filesystem/fsp/fsp_ldr.h"
+
+namespace Service::FileSystem {
+
+FSP_LDR::FSP_LDR(Core::System& system_) : ServiceFramework{system_, "fsp:ldr"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {0, nullptr, "OpenCodeFileSystem"},
+        {1, nullptr, "IsArchivedProgram"},
+        {2, nullptr, "SetCurrentProcess"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+FSP_LDR::~FSP_LDR() = default;
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.h b/src/core/hle/service/filesystem/fsp/fsp_ldr.h
new file mode 100755
index 000000000..358739a87
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::FileSystem {
+
+class FSP_LDR final : public ServiceFramework<FSP_LDR> {
+public:
+    explicit FSP_LDR(Core::System& system_);
+    ~FSP_LDR() override;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp
new file mode 100755
index 000000000..7c03ebaea
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/filesystem/fsp/fsp_pr.h"
+
+namespace Service::FileSystem {
+
+FSP_PR::FSP_PR(Core::System& system_) : ServiceFramework{system_, "fsp:pr"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {0, nullptr, "RegisterProgram"},
+        {1, nullptr, "UnregisterProgram"},
+        {2, nullptr, "SetCurrentProcess"},
+        {256, nullptr, "SetEnabledProgramVerification"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+FSP_PR::~FSP_PR() = default;
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.h b/src/core/hle/service/filesystem/fsp/fsp_pr.h
new file mode 100755
index 000000000..bd4e0a730
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_pr.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::FileSystem {
+
+class FSP_PR final : public ServiceFramework<FSP_PR> {
+public:
+    explicit FSP_PR(Core::System& system_);
+    ~FSP_PR() override;
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp
new file mode 100755
index 000000000..2be72b021
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp
@@ -0,0 +1,727 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cinttypes>
+#include <cstring>
+#include <iterator>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/fs_directory.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/system_archive/system_archive.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/result.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/fsp/fs_i_filesystem.h"
+#include "core/hle/service/filesystem/fsp/fs_i_storage.h"
+#include "core/hle/service/filesystem/fsp/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"
+
+namespace Service::FileSystem {
+enum class FileSystemType : u8 {
+    Invalid0 = 0,
+    Invalid1 = 1,
+    Logo = 2,
+    ContentControl = 3,
+    ContentManual = 4,
+    ContentMeta = 5,
+    ContentData = 6,
+    ApplicationPackage = 7,
+};
+
+class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
+public:
+    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"},
+        };
+        RegisterHandlers(functions);
+
+        FindAllSaves(space);
+    }
+
+    void ReadSaveDataInfo(HLERequestContext& ctx) {
+        LOG_DEBUG(Service_FS, "called");
+
+        // Calculate how many entries we can fit in the output buffer
+        const u64 count_entries = ctx.GetWriteBufferNumElements<SaveDataInfo>();
+
+        // Cap at total number of entries.
+        const u64 actual_entries = std::min(count_entries, info.size() - next_entry_index);
+
+        // Determine data start and end
+        const auto* begin = reinterpret_cast<u8*>(info.data() + next_entry_index);
+        const auto* end = reinterpret_cast<u8*>(info.data() + next_entry_index + actual_entries);
+        const auto range_size = static_cast<std::size_t>(std::distance(begin, end));
+
+        next_entry_index += actual_entries;
+
+        // Write the data to memory
+        ctx.WriteBuffer(begin, range_size);
+
+        IPC::ResponseBuilder rb{ctx, 4};
+        rb.Push(ResultSuccess);
+        rb.Push<u64>(actual_entries);
+    }
+
+private:
+    static u64 stoull_be(std::string_view str) {
+        if (str.size() != 16)
+            return 0;
+
+        const auto bytes = Common::HexStringToArray<0x8>(str);
+        u64 out{};
+        std::memcpy(&out, bytes.data(), sizeof(u64));
+
+        return Common::swap64(out);
+    }
+
+    void FindAllSaves(FileSys::SaveDataSpaceId space) {
+        FileSys::VirtualDir save_root{};
+        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);
+            return;
+        }
+
+        for (const auto& type : save_root->GetSubdirectories()) {
+            if (type->GetName() == "save") {
+                for (const auto& save_id : type->GetSubdirectories()) {
+                    for (const auto& user_id : save_id->GetSubdirectories()) {
+                        const auto save_id_numeric = stoull_be(save_id->GetName());
+                        auto user_id_numeric = Common::HexStringToArray<0x10>(user_id->GetName());
+                        std::reverse(user_id_numeric.begin(), user_id_numeric.end());
+
+                        if (save_id_numeric != 0) {
+                            // System Save Data
+                            info.emplace_back(SaveDataInfo{
+                                0,
+                                space,
+                                FileSys::SaveDataType::SystemSaveData,
+                                {},
+                                user_id_numeric,
+                                save_id_numeric,
+                                0,
+                                user_id->GetSize(),
+                                {},
+                                {},
+                            });
+
+                            continue;
+                        }
+
+                        for (const auto& title_id : user_id->GetSubdirectories()) {
+                            const auto device =
+                                std::all_of(user_id_numeric.begin(), user_id_numeric.end(),
+                                            [](u8 val) { return val == 0; });
+                            info.emplace_back(SaveDataInfo{
+                                0,
+                                space,
+                                device ? FileSys::SaveDataType::DeviceSaveData
+                                       : FileSys::SaveDataType::SaveData,
+                                {},
+                                user_id_numeric,
+                                save_id_numeric,
+                                stoull_be(title_id->GetName()),
+                                title_id->GetSize(),
+                                {},
+                                {},
+                            });
+                        }
+                    }
+                }
+            } else if (space == FileSys::SaveDataSpaceId::TemporaryStorage) {
+                // Temporary Storage
+                for (const auto& user_id : type->GetSubdirectories()) {
+                    for (const auto& title_id : user_id->GetSubdirectories()) {
+                        if (!title_id->GetFiles().empty() ||
+                            !title_id->GetSubdirectories().empty()) {
+                            auto user_id_numeric =
+                                Common::HexStringToArray<0x10>(user_id->GetName());
+                            std::reverse(user_id_numeric.begin(), user_id_numeric.end());
+
+                            info.emplace_back(SaveDataInfo{
+                                0,
+                                space,
+                                FileSys::SaveDataType::TemporaryStorage,
+                                {},
+                                user_id_numeric,
+                                stoull_be(type->GetName()),
+                                stoull_be(title_id->GetName()),
+                                title_id->GetSize(),
+                                {},
+                                {},
+                            });
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    struct SaveDataInfo {
+        u64_le save_id_unknown;
+        FileSys::SaveDataSpaceId space;
+        FileSys::SaveDataType type;
+        INSERT_PADDING_BYTES(0x6);
+        std::array<u8, 0x10> user_id;
+        u64_le save_id;
+        u64_le title_id;
+        u64_le save_image_size;
+        u16_le index;
+        FileSys::SaveDataRank rank;
+        INSERT_PADDING_BYTES(0x25);
+    };
+    static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
+
+    ProcessId process_id = 0;
+    std::shared_ptr<SaveDataController> save_data_controller;
+    std::vector<SaveDataInfo> info;
+    u64 next_entry_index = 0;
+};
+
+FSP_SRV::FSP_SRV(Core::System& system_)
+    : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()},
+      content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {0, nullptr, "OpenFileSystem"},
+        {1, &FSP_SRV::SetCurrentProcess, "SetCurrentProcess"},
+        {2, nullptr, "OpenDataFileSystemByCurrentProcess"},
+        {7, &FSP_SRV::OpenFileSystemWithPatch, "OpenFileSystemWithPatch"},
+        {8, nullptr, "OpenFileSystemWithId"},
+        {9, nullptr, "OpenDataFileSystemByApplicationId"},
+        {11, nullptr, "OpenBisFileSystem"},
+        {12, nullptr, "OpenBisStorage"},
+        {13, nullptr, "InvalidateBisCache"},
+        {17, nullptr, "OpenHostFileSystem"},
+        {18, &FSP_SRV::OpenSdCardFileSystem, "OpenSdCardFileSystem"},
+        {19, nullptr, "FormatSdCardFileSystem"},
+        {21, nullptr, "DeleteSaveDataFileSystem"},
+        {22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
+        {23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
+        {24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
+        {25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
+        {26, nullptr, "FormatSdCardDryRun"},
+        {27, nullptr, "IsExFatSupported"},
+        {28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"},
+        {30, nullptr, "OpenGameCardStorage"},
+        {31, nullptr, "OpenGameCardFileSystem"},
+        {32, nullptr, "ExtendSaveDataFileSystem"},
+        {33, nullptr, "DeleteCacheStorage"},
+        {34, &FSP_SRV::GetCacheStorageSize, "GetCacheStorageSize"},
+        {35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
+        {36, nullptr, "OpenHostFileSystemWithOption"},
+        {51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
+        {52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
+        {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
+        {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
+        {58, nullptr, "ReadSaveDataFileSystemExtraData"},
+        {59, nullptr, "WriteSaveDataFileSystemExtraData"},
+        {60, nullptr, "OpenSaveDataInfoReader"},
+        {61, &FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
+        {62, &FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage, "OpenSaveDataInfoReaderOnlyCacheStorage"},
+        {64, nullptr, "OpenSaveDataInternalStorageFileSystem"},
+        {65, nullptr, "UpdateSaveDataMacForDebug"},
+        {66, nullptr, "WriteSaveDataFileSystemExtraData2"},
+        {67, nullptr, "FindSaveDataWithFilter"},
+        {68, nullptr, "OpenSaveDataInfoReaderBySaveDataFilter"},
+        {69, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataAttribute"},
+        {70, &FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute, "WriteSaveDataFileSystemExtraDataBySaveDataAttribute"},
+        {71, &FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, "ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute"},
+        {80, nullptr, "OpenSaveDataMetaFile"},
+        {81, nullptr, "OpenSaveDataTransferManager"},
+        {82, nullptr, "OpenSaveDataTransferManagerVersion2"},
+        {83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
+        {84, nullptr, "ListApplicationAccessibleSaveDataOwnerId"},
+        {85, nullptr, "OpenSaveDataTransferManagerForSaveDataRepair"},
+        {86, nullptr, "OpenSaveDataMover"},
+        {87, nullptr, "OpenSaveDataTransferManagerForRepair"},
+        {100, nullptr, "OpenImageDirectoryFileSystem"},
+        {101, nullptr, "OpenBaseFileSystem"},
+        {102, nullptr, "FormatBaseFileSystem"},
+        {110, nullptr, "OpenContentStorageFileSystem"},
+        {120, nullptr, "OpenCloudBackupWorkStorageFileSystem"},
+        {130, nullptr, "OpenCustomStorageFileSystem"},
+        {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
+        {201, nullptr, "OpenDataStorageByProgramId"},
+        {202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
+        {203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"},
+        {204, nullptr, "OpenDataFileSystemByProgramIndex"},
+        {205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"},
+        {206, nullptr, "OpenDataStorageByPath"},
+        {400, nullptr, "OpenDeviceOperator"},
+        {500, nullptr, "OpenSdCardDetectionEventNotifier"},
+        {501, nullptr, "OpenGameCardDetectionEventNotifier"},
+        {510, nullptr, "OpenSystemDataUpdateEventNotifier"},
+        {511, nullptr, "NotifySystemDataUpdateEvent"},
+        {520, nullptr, "SimulateGameCardDetectionEvent"},
+        {600, nullptr, "SetCurrentPosixTime"},
+        {601, nullptr, "QuerySaveDataTotalSize"},
+        {602, nullptr, "VerifySaveDataFileSystem"},
+        {603, nullptr, "CorruptSaveDataFileSystem"},
+        {604, nullptr, "CreatePaddingFile"},
+        {605, nullptr, "DeleteAllPaddingFiles"},
+        {606, nullptr, "GetRightsId"},
+        {607, nullptr, "RegisterExternalKey"},
+        {608, nullptr, "UnregisterAllExternalKey"},
+        {609, nullptr, "GetRightsIdByPath"},
+        {610, nullptr, "GetRightsIdAndKeyGenerationByPath"},
+        {611, nullptr, "SetCurrentPosixTimeWithTimeDifference"},
+        {612, nullptr, "GetFreeSpaceSizeForSaveData"},
+        {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"},
+        {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
+        {615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
+        {616, nullptr, "GetSaveDataCommitId"},
+        {617, nullptr, "UnregisterExternalKey"},
+        {620, nullptr, "SetSdCardEncryptionSeed"},
+        {630, nullptr, "SetSdCardAccessibility"},
+        {631, nullptr, "IsSdCardAccessible"},
+        {640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
+        {700, nullptr, "OpenAccessFailureResolver"},
+        {701, nullptr, "GetAccessFailureDetectionEvent"},
+        {702, nullptr, "IsAccessFailureDetected"},
+        {710, nullptr, "ResolveAccessFailure"},
+        {720, nullptr, "AbandonAccessFailure"},
+        {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"},
+        {810, nullptr, "RegisterProgramIndexMapInfo"},
+        {1000, nullptr, "SetBisRootForHost"},
+        {1001, nullptr, "SetSaveDataSize"},
+        {1002, nullptr, "SetSaveDataRootPath"},
+        {1003, &FSP_SRV::DisableAutoSaveDataCreation, "DisableAutoSaveDataCreation"},
+        {1004, &FSP_SRV::SetGlobalAccessLogMode, "SetGlobalAccessLogMode"},
+        {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"},
+        {1006, &FSP_SRV::OutputAccessLogToSdCard, "OutputAccessLogToSdCard"},
+        {1007, nullptr, "RegisterUpdatePartition"},
+        {1008, nullptr, "OpenRegisteredUpdatePartition"},
+        {1009, nullptr, "GetAndClearMemoryReportInfo"},
+        {1010, nullptr, "SetDataStorageRedirectTarget"},
+        {1011, &FSP_SRV::GetProgramIndexForAccessLog, "GetProgramIndexForAccessLog"},
+        {1012, nullptr, "GetFsStackUsage"},
+        {1013, nullptr, "UnsetSaveDataRootPath"},
+        {1014, nullptr, "OutputMultiProgramTagAccessLog"},
+        {1016, nullptr, "FlushAccessLogOnSdCard"},
+        {1017, nullptr, "OutputApplicationInfoAccessLog"},
+        {1018, nullptr, "SetDebugOption"},
+        {1019, nullptr, "UnsetDebugOption"},
+        {1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"},
+        {1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"},
+        {1200, &FSP_SRV::OpenMultiCommitManager, "OpenMultiCommitManager"},
+        {1300, nullptr, "OpenBisWiper"},
+    };
+    // clang-format on
+    RegisterHandlers(functions);
+
+    if (Settings::values.enable_fs_access_log) {
+        access_log_mode = AccessLogMode::SdCard;
+    }
+}
+
+FSP_SRV::~FSP_SRV() = default;
+
+void FSP_SRV::SetCurrentProcess(HLERequestContext& ctx) {
+    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(res);
+}
+
+void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto type = rp.PopRaw<FileSystemType>();
+    const auto title_id = rp.PopRaw<u64>();
+    LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", type, title_id);
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 0};
+    rb.Push(ResultUnknown);
+}
+
+void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    FileSys::VirtualDir sdmc_dir{};
+    fsc.OpenSDMC(&sdmc_dir);
+
+    auto filesystem = std::make_shared<IFileSystem>(
+        system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
+void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
+    [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+    u128 uid = rp.PopRaw<u128>();
+
+    LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
+              uid[1], uid[0]);
+
+    FileSys::VirtualDir save_data_dir{};
+    save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser,
+                                         save_struct);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    auto save_struct = rp.PopRaw<FileSys::SaveDataAttribute>();
+    [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+
+    LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
+
+    FileSys::VirtualDir save_data_dir{};
+    save_data_controller->CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandSystem,
+                                         save_struct);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    struct Parameters {
+        FileSys::SaveDataSpaceId space_id;
+        FileSys::SaveDataAttribute attribute;
+    };
+
+    const auto parameters = rp.PopRaw<Parameters>();
+
+    LOG_INFO(Service_FS, "called.");
+
+    FileSys::VirtualDir dir{};
+    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::ResultTargetNotFound);
+        return;
+    }
+
+    FileSys::StorageId id{};
+    switch (parameters.space_id) {
+    case FileSys::SaveDataSpaceId::NandUser:
+        id = FileSys::StorageId::NandUser;
+        break;
+    case FileSys::SaveDataSpaceId::SdCardSystem:
+    case FileSys::SaveDataSpaceId::SdCardUser:
+        id = FileSys::StorageId::SdCard;
+        break;
+    case FileSys::SaveDataSpaceId::NandSystem:
+        id = FileSys::StorageId::NandSystem;
+        break;
+    case FileSys::SaveDataSpaceId::TemporaryStorage:
+    case FileSys::SaveDataSpaceId::ProperSystem:
+    case FileSys::SaveDataSpaceId::SafeMode:
+        ASSERT(false);
+    }
+
+    auto filesystem =
+        std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id));
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
+}
+
+void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
+    OpenSaveDataFileSystem(ctx);
+}
+
+void FSP_SRV::OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
+    OpenSaveDataFileSystem(ctx);
+}
+
+void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto space = rp.PopRaw<FileSys::SaveDataSpaceId>();
+    LOG_INFO(Service_FS, "called, space={}", space);
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<ISaveDataInfoReader>(
+        std::make_shared<ISaveDataInfoReader>(system, save_data_controller, space));
+}
+
+void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called");
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<ISaveDataInfoReader>(system, save_data_controller,
+                                             FileSys::SaveDataSpaceId::TemporaryStorage);
+}
+
+void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) {
+    LOG_WARNING(Service_FS, "(STUBBED) called.");
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    struct Parameters {
+        FileSys::SaveDataSpaceId space_id;
+        FileSys::SaveDataAttribute attribute;
+    };
+
+    const auto parameters = rp.PopRaw<Parameters>();
+    // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData
+    constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None);
+
+    LOG_WARNING(Service_FS,
+                "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n"
+                "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n"
+                "attribute.type={}, attribute.rank={}, attribute.index={}",
+                flags, parameters.space_id, parameters.attribute.title_id,
+                parameters.attribute.user_id[1], parameters.attribute.user_id[0],
+                parameters.attribute.save_id, parameters.attribute.type, parameters.attribute.rank,
+                parameters.attribute.index);
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(flags);
+}
+
+void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    if (!romfs) {
+        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!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ResultUnknown);
+            return;
+        }
+
+        romfs = current_romfs;
+    }
+
+    auto storage = std::make_shared<IStorage>(system, romfs);
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IStorage>(std::move(storage));
+}
+
+void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto storage_id = rp.PopRaw<FileSys::StorageId>();
+    const auto unknown = rp.PopRaw<u32>();
+    const auto title_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
+              storage_id, unknown, title_id);
+
+    auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
+
+    if (!data) {
+        const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
+
+        if (archive != nullptr) {
+            IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+            rb.Push(ResultSuccess);
+            rb.PushIpcInterface(std::make_shared<IStorage>(system, archive));
+            return;
+        }
+
+        // TODO(DarkLordZach): Find the right error code to use here
+        LOG_ERROR(Service_FS,
+                  "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
+                  storage_id);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
+    const FileSys::PatchManager pm{title_id, fsc, content_provider};
+
+    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));
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IStorage>(std::move(storage));
+}
+
+void FSP_SRV::OpenPatchDataStorageByCurrentProcess(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto storage_id = rp.PopRaw<FileSys::StorageId>();
+    const auto title_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", storage_id, title_id);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(FileSys::ResultTargetNotFound);
+}
+
+void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const auto program_index = rp.PopRaw<u8>();
+
+    LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
+
+    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
+        LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
+    auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs));
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IStorage>(std::move(storage));
+}
+
+void FSP_SRV::DisableAutoSaveDataCreation(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    save_data_controller->SetAutoCreate(false);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::SetGlobalAccessLogMode(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    access_log_mode = rp.PopEnum<AccessLogMode>();
+
+    LOG_DEBUG(Service_FS, "called, access_log_mode={}", access_log_mode);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::GetGlobalAccessLogMode(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(access_log_mode);
+}
+
+void FSP_SRV::OutputAccessLogToSdCard(HLERequestContext& ctx) {
+    const auto raw = ctx.ReadBufferCopy();
+    auto log = Common::StringFromFixedZeroTerminatedBuffer(
+        reinterpret_cast<const char*>(raw.data()), raw.size());
+
+    LOG_DEBUG(Service_FS, "called");
+
+    reporter.SaveFSAccessLog(log);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void FSP_SRV::GetProgramIndexForAccessLog(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(AccessLogVersion::Latest);
+    rb.Push(access_log_program_index);
+}
+
+void FSP_SRV::GetCacheStorageSize(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto index{rp.Pop<s32>()};
+
+    LOG_WARNING(Service_FS, "(STUBBED) called with index={}", index);
+
+    IPC::ResponseBuilder rb{ctx, 6};
+    rb.Push(ResultSuccess);
+    rb.Push(s64{0});
+    rb.Push(s64{0});
+}
+
+class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> {
+public:
+    explicit IMultiCommitManager(Core::System& system_)
+        : ServiceFramework{system_, "IMultiCommitManager"} {
+        static const FunctionInfo functions[] = {
+            {1, &IMultiCommitManager::Add, "Add"},
+            {2, &IMultiCommitManager::Commit, "Commit"},
+        };
+        RegisterHandlers(functions);
+    }
+
+private:
+    FileSys::VirtualFile backend;
+
+    void Add(HLERequestContext& ctx) {
+        LOG_WARNING(Service_FS, "(STUBBED) called");
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultSuccess);
+    }
+
+    void Commit(HLERequestContext& ctx) {
+        LOG_WARNING(Service_FS, "(STUBBED) called");
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultSuccess);
+    }
+};
+
+void FSP_SRV::OpenMultiCommitManager(HLERequestContext& ctx) {
+    LOG_DEBUG(Service_FS, "called");
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>(system));
+}
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.h b/src/core/hle/service/filesystem/fsp/fsp_srv.h
new file mode 100755
index 000000000..26980af99
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_srv.h
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include "core/hle/service/service.h"
+
+namespace Core {
+class Reporter;
+}
+
+namespace FileSys {
+class ContentProvider;
+class FileSystemBackend;
+} // namespace FileSys
+
+namespace Service::FileSystem {
+
+class RomFsController;
+class SaveDataController;
+
+enum class AccessLogVersion : u32 {
+    V7_0_0 = 2,
+
+    Latest = V7_0_0,
+};
+
+enum class AccessLogMode : u32 {
+    None,
+    Log,
+    SdCard,
+};
+
+class FSP_SRV final : public ServiceFramework<FSP_SRV> {
+public:
+    explicit FSP_SRV(Core::System& system_);
+    ~FSP_SRV() override;
+
+private:
+    void SetCurrentProcess(HLERequestContext& ctx);
+    void OpenFileSystemWithPatch(HLERequestContext& ctx);
+    void OpenSdCardFileSystem(HLERequestContext& ctx);
+    void CreateSaveDataFileSystem(HLERequestContext& ctx);
+    void CreateSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
+    void OpenSaveDataFileSystem(HLERequestContext& ctx);
+    void OpenSaveDataFileSystemBySystemSaveDataId(HLERequestContext& ctx);
+    void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
+    void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
+    void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);
+    void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx);
+    void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(HLERequestContext& ctx);
+    void OpenDataStorageByCurrentProcess(HLERequestContext& ctx);
+    void OpenDataStorageByDataId(HLERequestContext& ctx);
+    void OpenPatchDataStorageByCurrentProcess(HLERequestContext& ctx);
+    void OpenDataStorageWithProgramIndex(HLERequestContext& ctx);
+    void DisableAutoSaveDataCreation(HLERequestContext& ctx);
+    void SetGlobalAccessLogMode(HLERequestContext& ctx);
+    void GetGlobalAccessLogMode(HLERequestContext& ctx);
+    void OutputAccessLogToSdCard(HLERequestContext& ctx);
+    void GetProgramIndexForAccessLog(HLERequestContext& ctx);
+    void OpenMultiCommitManager(HLERequestContext& ctx);
+    void GetCacheStorageSize(HLERequestContext& ctx);
+
+    FileSystemController& fsc;
+    const FileSys::ContentProvider& content_provider;
+    const Core::Reporter& reporter;
+
+    FileSys::VirtualFile romfs;
+    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/fsp/fsp_util.h b/src/core/hle/service/filesystem/fsp/fsp_util.h
new file mode 100755
index 000000000..253f866db
--- /dev/null
+++ b/src/core/hle/service/filesystem/fsp/fsp_util.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/service/filesystem/filesystem.h"
+
+namespace Service::FileSystem {
+
+struct SizeGetter {
+    std::function<u64()> get_free_size;
+    std::function<u64()> get_total_size;
+
+    static SizeGetter FromStorageId(const FileSystemController& fsc, FileSys::StorageId id) {
+        return {
+            [&fsc, id] { return fsc.GetFreeSpaceSize(id); },
+            [&fsc, id] { return fsc.GetTotalSpaceSize(id); },
+        };
+    }
+};
+
+} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/romfs_controller.h b/src/core/hle/service/filesystem/romfs_controller.h
index 9a478f71d..3c3ead344 100755
--- a/src/core/hle/service/filesystem/romfs_controller.h
+++ b/src/core/hle/service/filesystem/romfs_controller.h
@@ -5,7 +5,7 @@
 
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/romfs_factory.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 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
index d19b3ea1e..03e45f7f9 100755
--- a/src/core/hle/service/filesystem/save_data_controller.cpp
+++ b/src/core/hle/service/filesystem/save_data_controller.cpp
@@ -44,7 +44,7 @@ Result SaveDataController::CreateSaveData(FileSys::VirtualDir* out_save_data,
 
     auto save_data = factory->Create(space, attribute);
     if (save_data == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     *out_save_data = save_data;
@@ -56,7 +56,7 @@ Result SaveDataController::OpenSaveData(FileSys::VirtualDir* out_save_data,
                                         const FileSys::SaveDataAttribute& attribute) {
     auto save_data = factory->Open(space, attribute);
     if (save_data == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     *out_save_data = save_data;
@@ -67,7 +67,7 @@ Result SaveDataController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_
                                              FileSys::SaveDataSpaceId space) {
     auto save_data_space = factory->GetSaveDataSpaceDirectory(space);
     if (save_data_space == nullptr) {
-        return FileSys::ERROR_ENTITY_NOT_FOUND;
+        return FileSys::ResultTargetNotFound;
     }
 
     *out_save_data_space = save_data_space;
diff --git a/src/core/hle/service/filesystem/save_data_controller.h b/src/core/hle/service/filesystem/save_data_controller.h
index 863188e4c..dc9d713df 100755
--- a/src/core/hle/service/filesystem/save_data_controller.h
+++ b/src/core/hle/service/filesystem/save_data_controller.h
@@ -5,7 +5,7 @@
 
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/savedata_factory.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Service::FileSystem {
 
diff --git a/src/core/hle/service/glue/time/manager.cpp b/src/core/hle/service/glue/time/manager.cpp
index 6423e5089..b56762941 100755
--- a/src/core/hle/service/glue/time/manager.cpp
+++ b/src/core/hle/service/glue/time/manager.cpp
@@ -8,7 +8,7 @@
 
 #include "common/settings.h"
 #include "common/time_zone.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/kernel/svc.h"
 #include "core/hle/service/glue/time/manager.h"
 #include "core/hle/service/glue/time/time_zone_binary.h"
diff --git a/src/core/hle/service/glue/time/manager.h b/src/core/hle/service/glue/time/manager.h
index a46ec6364..1de93f8f9 100755
--- a/src/core/hle/service/glue/time/manager.h
+++ b/src/core/hle/service/glue/time/manager.h
@@ -7,7 +7,7 @@
 #include <string>
 
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "core/hle/service/glue/time/file_timestamp_worker.h"
 #include "core/hle/service/glue/time/standard_steady_clock_resource.h"
 #include "core/hle/service/glue/time/worker.h"
diff --git a/src/core/hle/service/glue/time/time_zone_binary.cpp b/src/core/hle/service/glue/time/time_zone_binary.cpp
index 67969aa3f..d33f784c0 100755
--- a/src/core/hle/service/glue/time/time_zone_binary.cpp
+++ b/src/core/hle/service/glue/time/time_zone_binary.cpp
@@ -7,7 +7,7 @@
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/system_archive/system_archive.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/glue/time/time_zone_binary.h"
 
diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp
index e491dd260..50e1ed756 100755
--- a/src/core/hle/service/hle_ipc.cpp
+++ b/src/core/hle/service/hle_ipc.cpp
@@ -501,6 +501,22 @@ bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const {
     }
 }
 
+void HLERequestContext::AddMoveInterface(SessionRequestHandlerPtr s) {
+    ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(
+        Kernel::LimitableResource::SessionCountMax, 1));
+
+    auto* session = Kernel::KSession::Create(kernel);
+    session->Initialize(nullptr, 0);
+    Kernel::KSession::Register(kernel, session);
+
+    auto& server = manager.lock()->GetServerManager();
+    auto next_manager = std::make_shared<Service::SessionRequestManager>(kernel, server);
+    next_manager->SetSessionHandler(std::move(s));
+    server.RegisterSession(&session->GetServerSession(), next_manager);
+
+    AddMoveObject(&session->GetClientSession());
+}
+
 std::string HLERequestContext::Description() const {
     if (!command_header) {
         return "No command header available";
diff --git a/src/core/hle/service/hle_ipc.h b/src/core/hle/service/hle_ipc.h
index 8329d7265..c2e0e5e8c 100755
--- a/src/core/hle/service/hle_ipc.h
+++ b/src/core/hle/service/hle_ipc.h
@@ -339,6 +339,8 @@ public:
         outgoing_move_objects.emplace_back(object);
     }
 
+    void AddMoveInterface(SessionRequestHandlerPtr s);
+
     void AddCopyObject(Kernel::KAutoObject* object) {
         outgoing_copy_objects.emplace_back(object);
     }
diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp
index 771563d4d..eef02ca96 100755
--- a/src/core/hle/service/jit/jit.cpp
+++ b/src/core/hle/service/jit/jit.cpp
@@ -6,12 +6,12 @@
 #include "core/core.h"
 #include "core/hle/kernel/k_transfer_memory.h"
 #include "core/hle/result.h"
+#include "core/hle/service/cmif_serialization.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/hle/service/jit/jit.h"
 #include "core/hle/service/jit/jit_code_memory.h"
 #include "core/hle/service/jit/jit_context.h"
 #include "core/hle/service/server_manager.h"
-#include "core/hle/service/service.h"
 #include "core/memory.h"
 
 namespace Service::JIT {
@@ -21,6 +21,9 @@ struct CodeRange {
     u64 size;
 };
 
+using Struct32 = std::array<u64, 4>;
+static_assert(sizeof(Struct32) == 32, "Struct32 has wrong size");
+
 class IJitEnvironment final : public ServiceFramework<IJitEnvironment> {
 public:
     explicit IJitEnvironment(Core::System& system_,
@@ -29,12 +32,13 @@ public:
         : ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)},
           user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)},
           context{system_.ApplicationMemory()} {
+
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, &IJitEnvironment::GenerateCode, "GenerateCode"},
-            {1, &IJitEnvironment::Control, "Control"},
-            {1000, &IJitEnvironment::LoadPlugin, "LoadPlugin"},
-            {1001, &IJitEnvironment::GetCodeAddress, "GetCodeAddress"},
+            {0, C<&IJitEnvironment::GenerateCode>, "GenerateCode"},
+            {1, C<&IJitEnvironment::Control>, "Control"},
+            {1000, C<&IJitEnvironment::LoadPlugin>, "LoadPlugin"},
+            {1001, C<&IJitEnvironment::GetCodeAddress>, "GetCodeAddress"},
         };
         // clang-format on
 
@@ -50,28 +54,10 @@ public:
         configuration.sys_ro_memory = configuration.user_ro_memory;
     }
 
-    void GenerateCode(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_JIT, "called");
-
-        struct InputParameters {
-            u32 data_size;
-            u64 command;
-            std::array<CodeRange, 2> ranges;
-            Struct32 data;
-        };
-
-        struct OutputParameters {
-            s32 return_value;
-            std::array<CodeRange, 2> ranges;
-        };
-
-        IPC::RequestParser rp{ctx};
-        const auto parameters{rp.PopRaw<InputParameters>()};
-
-        // Optional input/output buffers
-        const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span<const u8>()};
-        std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
-
+    Result GenerateCode(Out<s32> out_return_value, Out<CodeRange> out_range0,
+                        Out<CodeRange> out_range1, OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+                        u32 data_size, u64 command, CodeRange range0, CodeRange range1,
+                        Struct32 data, InBuffer<BufferAttr_HipcMapAlias> buffer) {
         // Function call prototype:
         // void GenerateCode(s32* ret, CodeRange* c0_out, CodeRange* c1_out, JITConfiguration* cfg,
         //                   u64 cmd, u8* input_buf, size_t input_size, CodeRange* c0_in,
@@ -83,66 +69,36 @@ public:
         // other arguments are used to transfer state between the game and the plugin.
 
         const VAddr ret_ptr{context.AddHeap(0u)};
-        const VAddr c0_in_ptr{context.AddHeap(parameters.ranges[0])};
-        const VAddr c1_in_ptr{context.AddHeap(parameters.ranges[1])};
-        const VAddr c0_out_ptr{context.AddHeap(ClearSize(parameters.ranges[0]))};
-        const VAddr c1_out_ptr{context.AddHeap(ClearSize(parameters.ranges[1]))};
+        const VAddr c0_in_ptr{context.AddHeap(range0)};
+        const VAddr c1_in_ptr{context.AddHeap(range1)};
+        const VAddr c0_out_ptr{context.AddHeap(ClearSize(range0))};
+        const VAddr c1_out_ptr{context.AddHeap(ClearSize(range1))};
 
-        const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())};
-        const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())};
-        const VAddr data_ptr{context.AddHeap(parameters.data)};
+        const VAddr input_ptr{context.AddHeap(buffer.data(), buffer.size())};
+        const VAddr output_ptr{context.AddHeap(out_buffer.data(), out_buffer.size())};
+        const VAddr data_ptr{context.AddHeap(data)};
         const VAddr configuration_ptr{context.AddHeap(configuration)};
 
         // The callback does not directly return a value, it only writes to the output pointer
         context.CallFunction(callbacks.GenerateCode, ret_ptr, c0_out_ptr, c1_out_ptr,
-                             configuration_ptr, parameters.command, input_ptr, input_buffer.size(),
-                             c0_in_ptr, c1_in_ptr, data_ptr, parameters.data_size, output_ptr,
-                             output_buffer.size());
+                             configuration_ptr, command, input_ptr, buffer.size(), c0_in_ptr,
+                             c1_in_ptr, data_ptr, data_size, output_ptr, out_buffer.size());
 
-        const s32 return_value{context.GetHeap<s32>(ret_ptr)};
+        *out_return_value = context.GetHeap<s32>(ret_ptr);
+        *out_range0 = context.GetHeap<CodeRange>(c0_out_ptr);
+        *out_range1 = context.GetHeap<CodeRange>(c1_out_ptr);
+        context.GetHeap(output_ptr, out_buffer.data(), out_buffer.size());
 
-        if (return_value == 0) {
-            // The callback has written to the output executable code range,
-            // requiring an instruction cache invalidation
-            Core::InvalidateInstructionCacheRange(process.GetPointerUnsafe(),
-                                                  configuration.user_rx_memory.offset,
-                                                  configuration.user_rx_memory.size);
-
-            // Write back to the IPC output buffer, if provided
-            if (ctx.CanWriteBuffer()) {
-                context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size());
-                ctx.WriteBuffer(output_buffer.data(), output_buffer.size());
-            }
-
-            const OutputParameters out{
-                .return_value = return_value,
-                .ranges =
-                    {
-                        context.GetHeap<CodeRange>(c0_out_ptr),
-                        context.GetHeap<CodeRange>(c1_out_ptr),
-                    },
-            };
-
-            IPC::ResponseBuilder rb{ctx, 8};
-            rb.Push(ResultSuccess);
-            rb.PushRaw(out);
-        } else {
+        if (*out_return_value != 0) {
             LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
+            R_THROW(ResultUnknown);
         }
-    };
 
-    void Control(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_JIT, "called");
-
-        IPC::RequestParser rp{ctx};
-        const auto command{rp.PopRaw<u64>()};
-
-        // Optional input/output buffers
-        const auto input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::span<const u8>()};
-        std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0);
+        R_SUCCEED();
+    }
 
+    Result Control(Out<s32> out_return_value, InBuffer<BufferAttr_HipcMapAlias> in_data,
+                   OutBuffer<BufferAttr_HipcMapAlias> out_data, u64 command) {
         // Function call prototype:
         // u64 Control(s32* ret, JITConfiguration* cfg, u64 cmd, u8* input_buf, size_t input_size,
         //             u8* output_buf, size_t output_size);
@@ -152,53 +108,30 @@ public:
 
         const VAddr ret_ptr{context.AddHeap(0u)};
         const VAddr configuration_ptr{context.AddHeap(configuration)};
-        const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())};
-        const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())};
+        const VAddr input_ptr{context.AddHeap(in_data.data(), in_data.size())};
+        const VAddr output_ptr{context.AddHeap(out_data.data(), out_data.size())};
 
         const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr,
-                                                     command, input_ptr, input_buffer.size(),
-                                                     output_ptr, output_buffer.size())};
+                                                     command, input_ptr, in_data.size(), output_ptr,
+                                                     out_data.size())};
 
-        const s32 return_value{context.GetHeap<s32>(ret_ptr)};
+        *out_return_value = context.GetHeap<s32>(ret_ptr);
+        context.GetHeap(output_ptr, out_data.data(), out_data.size());
 
-        if (wrapper_value == 0 && return_value == 0) {
-            // Write back to the IPC output buffer, if provided
-            if (ctx.CanWriteBuffer()) {
-                context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size());
-                ctx.WriteBuffer(output_buffer.data(), output_buffer.size());
-            }
-
-            IPC::ResponseBuilder rb{ctx, 3};
-            rb.Push(ResultSuccess);
-            rb.Push(return_value);
-        } else {
-            LOG_WARNING(Service_JIT, "plugin Control callback failed");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
+        if (wrapper_value == 0 && *out_return_value == 0) {
+            R_SUCCEED();
         }
+
+        LOG_WARNING(Service_JIT, "plugin Control callback failed");
+        R_THROW(ResultUnknown);
     }
 
-    void LoadPlugin(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_JIT, "called");
-
-        IPC::RequestParser rp{ctx};
-        const auto tmem_size{rp.PopRaw<u64>()};
-        const auto tmem_handle{ctx.GetCopyHandle(0)};
-        const auto nro_plugin{ctx.ReadBuffer(1)};
-
-        if (tmem_size == 0) {
-            LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
-        }
-
-        auto tmem{ctx.GetObjectFromHandle<Kernel::KTransferMemory>(tmem_handle)};
+    Result LoadPlugin(u64 tmem_size, InCopyHandle<Kernel::KTransferMemory>& tmem,
+                      InBuffer<BufferAttr_HipcMapAlias> nrr,
+                      InBuffer<BufferAttr_HipcMapAlias> nro) {
         if (tmem.IsNull()) {
-            LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            LOG_ERROR(Service_JIT, "Invalid transfer memory handle!");
+            R_THROW(ResultUnknown);
         }
 
         // Set up the configuration with the required TransferMemory address
@@ -206,7 +139,7 @@ public:
         configuration.transfer_memory.size = tmem_size;
 
         // Gather up all the callbacks from the loaded plugin
-        auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)};
+        auto symbols{Core::Symbols::GetSymbols(nro, true)};
         const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }};
 
         callbacks.rtld_fini = GetSymbol("_fini");
@@ -223,16 +156,12 @@ public:
         if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 ||
             callbacks.OnPrepared == 0) {
             LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            R_THROW(ResultUnknown);
         }
 
-        if (!context.LoadNRO(nro_plugin)) {
+        if (!context.LoadNRO(nro)) {
             LOG_ERROR(Service_JIT, "failed to load plugin");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            R_THROW(ResultUnknown);
         }
 
         context.MapProcessMemory(configuration.sys_ro_memory.offset,
@@ -252,9 +181,7 @@ public:
         const auto version{context.CallFunction(callbacks.GetVersion)};
         if (version != 1) {
             LOG_ERROR(Service_JIT, "unknown plugin version {}", version);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            R_THROW(ResultUnknown);
         }
 
         // Function prototype:
@@ -280,22 +207,19 @@ public:
         const auto configuration_ptr{context.AddHeap(configuration)};
         context.CallFunction(callbacks.OnPrepared, configuration_ptr);
 
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
+        R_SUCCEED();
     }
 
-    void GetCodeAddress(HLERequestContext& ctx) {
+    Result GetCodeAddress(Out<u64> rx_offset, Out<u64> ro_offset) {
         LOG_DEBUG(Service_JIT, "called");
 
-        IPC::ResponseBuilder rb{ctx, 6};
-        rb.Push(ResultSuccess);
-        rb.Push(configuration.user_rx_memory.offset);
-        rb.Push(configuration.user_ro_memory.offset);
+        *rx_offset = configuration.user_rx_memory.offset;
+        *ro_offset = configuration.user_ro_memory.offset;
+
+        R_SUCCEED();
     }
 
 private:
-    using Struct32 = std::array<u8, 32>;
-
     struct GuestCallbacks {
         VAddr rtld_fini;
         VAddr rtld_init;
@@ -335,7 +259,7 @@ public:
     explicit JITU(Core::System& system_) : ServiceFramework{system_, "jit:u"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {0, &JITU::CreateJitEnvironment, "CreateJitEnvironment"},
+            {0, C<&JITU::CreateJitEnvironment>, "CreateJitEnvironment"},
         };
         // clang-format on
 
@@ -343,76 +267,33 @@ public:
     }
 
 private:
-    void CreateJitEnvironment(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_JIT, "called");
-
-        struct Parameters {
-            u64 rx_size;
-            u64 ro_size;
-        };
-
-        IPC::RequestParser rp{ctx};
-        const auto parameters{rp.PopRaw<Parameters>()};
-        const auto process_handle{ctx.GetCopyHandle(0)};
-        const auto rx_mem_handle{ctx.GetCopyHandle(1)};
-        const auto ro_mem_handle{ctx.GetCopyHandle(2)};
-
-        if (parameters.rx_size == 0 || parameters.ro_size == 0) {
-            LOG_ERROR(Service_JIT, "attempted to init with empty code regions");
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
-        }
-
-        auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(process_handle)};
+    Result CreateJitEnvironment(Out<SharedPointer<IJitEnvironment>> out_jit_environment,
+                                u64 rx_size, u64 ro_size, InCopyHandle<Kernel::KProcess>& process,
+                                InCopyHandle<Kernel::KCodeMemory>& rx_mem,
+                                InCopyHandle<Kernel::KCodeMemory>& ro_mem) {
         if (process.IsNull()) {
-            LOG_ERROR(Service_JIT, "process is null for handle=0x{:08X}", process_handle);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            LOG_ERROR(Service_JIT, "process is null");
+            R_THROW(ResultUnknown);
         }
-
-        auto rx_mem{ctx.GetObjectFromHandle<Kernel::KCodeMemory>(rx_mem_handle)};
         if (rx_mem.IsNull()) {
-            LOG_ERROR(Service_JIT, "rx_mem is null for handle=0x{:08X}", rx_mem_handle);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+            LOG_ERROR(Service_JIT, "rx_mem is null");
+            R_THROW(ResultUnknown);
         }
-
-        auto ro_mem{ctx.GetObjectFromHandle<Kernel::KCodeMemory>(ro_mem_handle)};
-        if (ro_mem.IsNull()) {
-            LOG_ERROR(Service_JIT, "ro_mem is null for handle=0x{:08X}", ro_mem_handle);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(ResultUnknown);
-            return;
+        if (rx_mem.IsNull()) {
+            LOG_ERROR(Service_JIT, "ro_mem is null");
+            R_THROW(ResultUnknown);
         }
 
         CodeMemory rx, ro;
-        Result res;
 
-        res = rx.Initialize(*process, *rx_mem, parameters.rx_size,
-                            Kernel::Svc::MemoryPermission::ReadExecute, generate_random);
-        if (R_FAILED(res)) {
-            LOG_ERROR(Service_JIT, "rx_mem could not be mapped for handle=0x{:08X}", rx_mem_handle);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(res);
-            return;
-        }
+        R_TRY(rx.Initialize(*process, *rx_mem, rx_size, Kernel::Svc::MemoryPermission::ReadExecute,
+                            generate_random));
+        R_TRY(ro.Initialize(*process, *ro_mem, ro_size, Kernel::Svc::MemoryPermission::Read,
+                            generate_random));
 
-        res = ro.Initialize(*process, *ro_mem, parameters.ro_size,
-                            Kernel::Svc::MemoryPermission::Read, generate_random);
-        if (R_FAILED(res)) {
-            LOG_ERROR(Service_JIT, "ro_mem could not be mapped for handle=0x{:08X}", ro_mem_handle);
-            IPC::ResponseBuilder rb{ctx, 2};
-            rb.Push(res);
-            return;
-        }
-
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IJitEnvironment>(system, std::move(process), std::move(rx),
-                                             std::move(ro));
+        *out_jit_environment = std::make_shared<IJitEnvironment>(system, std::move(process),
+                                                                 std::move(rx), std::move(ro));
+        R_SUCCEED();
     }
 
 private:
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 207ac4efe..3e2c7deab 100755
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -301,7 +301,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
         return result;
     }
 
-    if (result.module != ErrorModule::NFC) {
+    if (result.GetModule() != ErrorModule::NFC) {
         return result;
     }
 
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index f868f9f3e..20ea7aa43 100755
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -6,7 +6,7 @@
 #include "core/core.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/patch_manager.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/glue/glue_manager.h"
 #include "core/hle/service/ipc_helpers.h"
diff --git a/src/core/hle/service/ro/ro.cpp b/src/core/hle/service/ro/ro.cpp
index f0658bb5d..ae62c430e 100755
--- a/src/core/hle/service/ro/ro.cpp
+++ b/src/core/hle/service/ro/ro.cpp
@@ -6,13 +6,13 @@
 #include "common/scope_exit.h"
 #include "core/hle/kernel/k_process.h"
 
+#include "core/hle/service/cmif_serialization.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/hle/service/ro/ro.h"
 #include "core/hle/service/ro/ro_nro_utils.h"
 #include "core/hle/service/ro/ro_results.h"
 #include "core/hle/service/ro/ro_types.h"
 #include "core/hle/service/server_manager.h"
-#include "core/hle/service/service.h"
 
 namespace Service::RO {
 
@@ -500,46 +500,65 @@ private:
     }
 };
 
-class RoInterface {
+class RoInterface : public ServiceFramework<RoInterface> {
 public:
-    explicit RoInterface(std::shared_ptr<RoContext> ro, NrrKind nrr_kind)
-        : m_ro(ro), m_context_id(InvalidContextId), m_nrr_kind(nrr_kind) {}
+    explicit RoInterface(Core::System& system_, const char* name_, std::shared_ptr<RoContext> ro,
+                         NrrKind nrr_kind)
+        : ServiceFramework{system_, name_}, m_ro(ro), m_context_id(InvalidContextId),
+          m_nrr_kind(nrr_kind) {
+
+        // clang-format off
+        static const FunctionInfo functions[] = {
+            {0,  C<&RoInterface::MapManualLoadModuleMemory>, "MapManualLoadModuleMemory"},
+            {1,  C<&RoInterface::UnmapManualLoadModuleMemory>, "UnmapManualLoadModuleMemory"},
+            {2,  C<&RoInterface::RegisterModuleInfo>, "RegisterModuleInfo"},
+            {3,  C<&RoInterface::UnregisterModuleInfo>, "UnregisterModuleInfo"},
+            {4,  C<&RoInterface::RegisterProcessHandle>, "RegisterProcessHandle"},
+            {10, C<&RoInterface::RegisterProcessModuleInfo>, "RegisterProcessModuleInfo"},
+        };
+        // clang-format on
+
+        RegisterHandlers(functions);
+    }
+
     ~RoInterface() {
         m_ro->UnregisterProcess(m_context_id);
     }
 
-    Result MapManualLoadModuleMemory(u64* out_load_address, u64 client_pid, u64 nro_address,
-                                     u64 nro_size, u64 bss_address, u64 bss_size) {
-        R_TRY(m_ro->ValidateProcess(m_context_id, client_pid));
-        R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address, m_context_id, nro_address,
+    Result MapManualLoadModuleMemory(Out<u64> out_load_address, ClientProcessId client_pid,
+                                     u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
+        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
+        R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address,
                                                  nro_size, bss_address, bss_size));
     }
 
-    Result UnmapManualLoadModuleMemory(u64 client_pid, u64 nro_address) {
-        R_TRY(m_ro->ValidateProcess(m_context_id, client_pid));
+    Result UnmapManualLoadModuleMemory(ClientProcessId client_pid, u64 nro_address) {
+        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
         R_RETURN(m_ro->UnmapManualLoadModuleMemory(m_context_id, nro_address));
     }
 
-    Result RegisterModuleInfo(u64 client_pid, u64 nrr_address, u64 nrr_size) {
-        R_TRY(m_ro->ValidateProcess(m_context_id, client_pid));
+    Result RegisterModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size) {
+        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
         R_RETURN(
             m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, NrrKind::User, true));
     }
 
-    Result UnregisterModuleInfo(u64 client_pid, u64 nrr_address) {
-        R_TRY(m_ro->ValidateProcess(m_context_id, client_pid));
+    Result UnregisterModuleInfo(ClientProcessId client_pid, u64 nrr_address) {
+        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
         R_RETURN(m_ro->UnregisterModuleInfo(m_context_id, nrr_address));
     }
 
-    Result RegisterProcessHandle(u64 client_pid, Kernel::KProcess* process) {
+    Result RegisterProcessHandle(ClientProcessId client_pid,
+                                 InCopyHandle<Kernel::KProcess>& process) {
         // Register the process.
-        R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process, client_pid));
+        R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process.GetPointerUnsafe(),
+                                       *client_pid));
     }
 
-    Result RegisterProcessModuleInfo(u64 client_pid, u64 nrr_address, u64 nrr_size,
-                                     Kernel::KProcess* process) {
+    Result RegisterProcessModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size,
+                                     InCopyHandle<Kernel::KProcess>& process) {
         // Validate the process.
-        R_TRY(m_ro->ValidateProcess(m_context_id, client_pid));
+        R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
 
         // Register the module.
         R_RETURN(m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, m_nrr_kind,
@@ -552,137 +571,6 @@ private:
     NrrKind m_nrr_kind{};
 };
 
-class IRoInterface : public ServiceFramework<IRoInterface> {
-public:
-    explicit IRoInterface(Core::System& system_, const char* name_, std::shared_ptr<RoContext> ro,
-                          NrrKind nrr_kind)
-        : ServiceFramework{system_, name_}, interface {
-        ro, nrr_kind
-    } {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &IRoInterface::MapManualLoadModuleMemory, "MapManualLoadModuleMemory"},
-            {1, &IRoInterface::UnmapManualLoadModuleMemory, "UnmapManualLoadModuleMemory"},
-            {2, &IRoInterface::RegisterModuleInfo, "RegisterModuleInfo"},
-            {3, &IRoInterface::UnregisterModuleInfo, "UnregisterModuleInfo"},
-            {4, &IRoInterface::RegisterProcessHandle, "RegisterProcessHandle"},
-            {10, &IRoInterface::RegisterProcessModuleInfo, "RegisterProcessModuleInfo"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-private:
-    void MapManualLoadModuleMemory(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        struct InputParameters {
-            u64 client_pid;
-            u64 nro_address;
-            u64 nro_size;
-            u64 bss_address;
-            u64 bss_size;
-        };
-
-        IPC::RequestParser rp{ctx};
-        auto params = rp.PopRaw<InputParameters>();
-
-        u64 load_address = 0;
-        auto result = interface.MapManualLoadModuleMemory(&load_address, ctx.GetPID(),
-                                                          params.nro_address, params.nro_size,
-                                                          params.bss_address, params.bss_size);
-
-        IPC::ResponseBuilder rb{ctx, 4};
-        rb.Push(result);
-        rb.Push(load_address);
-    }
-
-    void UnmapManualLoadModuleMemory(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        struct InputParameters {
-            u64 client_pid;
-            u64 nro_address;
-        };
-
-        IPC::RequestParser rp{ctx};
-        auto params = rp.PopRaw<InputParameters>();
-        auto result = interface.UnmapManualLoadModuleMemory(ctx.GetPID(), params.nro_address);
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-    }
-
-    void RegisterModuleInfo(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        struct InputParameters {
-            u64 client_pid;
-            u64 nrr_address;
-            u64 nrr_size;
-        };
-
-        IPC::RequestParser rp{ctx};
-        auto params = rp.PopRaw<InputParameters>();
-        auto result =
-            interface.RegisterModuleInfo(ctx.GetPID(), params.nrr_address, params.nrr_size);
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-    }
-
-    void UnregisterModuleInfo(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        struct InputParameters {
-            u64 client_pid;
-            u64 nrr_address;
-        };
-
-        IPC::RequestParser rp{ctx};
-        auto params = rp.PopRaw<InputParameters>();
-        auto result = interface.UnregisterModuleInfo(ctx.GetPID(), params.nrr_address);
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-    }
-
-    void RegisterProcessHandle(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(ctx.GetCopyHandle(0));
-        auto client_pid = ctx.GetPID();
-        auto result = interface.RegisterProcessHandle(client_pid, process.GetPointerUnsafe());
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-    }
-
-    void RegisterProcessModuleInfo(HLERequestContext& ctx) {
-        LOG_DEBUG(Service_LDR, "(called)");
-
-        struct InputParameters {
-            u64 client_pid;
-            u64 nrr_address;
-            u64 nrr_size;
-        };
-
-        IPC::RequestParser rp{ctx};
-        auto params = rp.PopRaw<InputParameters>();
-        auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(ctx.GetCopyHandle(0));
-
-        auto client_pid = ctx.GetPID();
-        auto result = interface.RegisterProcessModuleInfo(
-            client_pid, params.nrr_address, params.nrr_size, process.GetPointerUnsafe());
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-    }
-
-    RoInterface interface;
-};
-
 } // namespace
 
 void LoopProcess(Core::System& system) {
@@ -691,11 +579,11 @@ void LoopProcess(Core::System& system) {
     auto ro = std::make_shared<RoContext>();
 
     const auto RoInterfaceFactoryForUser = [&, ro] {
-        return std::make_shared<IRoInterface>(system, "ldr:ro", ro, NrrKind::User);
+        return std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User);
     };
 
     const auto RoInterfaceFactoryForJitPlugin = [&, ro] {
-        return std::make_shared<IRoInterface>(system, "ro:1", ro, NrrKind::JitPlugin);
+        return std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin);
     };
 
     server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser));
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 335e58614..f5ee433f8 100755
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -206,6 +206,22 @@ protected:
         RegisterHandlersBaseTipc(functions, n);
     }
 
+protected:
+    template <bool Domain, auto F>
+    void CmifReplyWrap(HLERequestContext& ctx);
+
+    /**
+     * Wraps the template pointer-to-member function for use in a domain session.
+     */
+    template <auto F>
+    static constexpr HandlerFnP<Self> D = &Self::template CmifReplyWrap<true, F>;
+
+    /**
+     * Wraps the template pointer-to-member function for use in a non-domain session.
+     */
+    template <auto F>
+    static constexpr HandlerFnP<Self> C = &Self::template CmifReplyWrap<false, F>;
+
 private:
     /**
      * This function is used to allow invocation of pointers to handlers stored in the base class
diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp
index f40a1c8f3..b527c39a9 100755
--- a/src/core/hle/service/set/system_settings_server.cpp
+++ b/src/core/hle/service/set/system_settings_server.cpp
@@ -67,13 +67,13 @@ Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System&
     const auto ver_file = romfs->GetFile("file");
     if (ver_file == nullptr) {
         return early_exit_failure("The system version archive didn't contain the file 'file'.",
-                                  FileSys::ERROR_INVALID_ARGUMENT);
+                                  FileSys::ResultInvalidArgument);
     }
 
     auto data = ver_file->ReadAllBytes();
     if (data.size() != sizeof(FirmwareVersionFormat)) {
         return early_exit_failure("The system version file 'file' was not the correct size.",
-                                  FileSys::ERROR_OUT_OF_BOUNDS);
+                                  FileSys::ResultOutOfRange);
     }
 
     std::memcpy(&out_firmware, data.data(), sizeof(FirmwareVersionFormat));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index af3fc8f9a..028830a62 100755
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -14,7 +14,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "core/file_sys/control_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Core {
 class System;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 969c0fc9f..70ad079a3 100755
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -12,7 +12,7 @@
 #include "core/core.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/romfs_factory.h"
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 #include "core/hle/kernel/code_set.h"
 #include "core/hle/kernel/k_page_table.h"
 #include "core/hle/kernel/k_process.h"
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index bfffbe2dc..a6ac1edf2 100755
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -68,8 +68,8 @@ json GetReportCommonData(u64 title_id, Result result, const std::string& timesta
     auto out = json{
         {"title_id", fmt::format("{:016X}", title_id)},
         {"result_raw", fmt::format("{:08X}", result.raw)},
-        {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
-        {"result_description", fmt::format("{:08X}", result.description.Value())},
+        {"result_module", fmt::format("{:08X}", static_cast<u32>(result.GetModule()))},
+        {"result_description", fmt::format("{:08X}", result.GetDescription())},
         {"timestamp", timestamp},
     };
 
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h
index 0b0fee73e..f3efe3465 100755
--- a/src/frontend_common/content_manager.h
+++ b/src/frontend_common/content_manager.h
@@ -9,7 +9,7 @@
 #include "core/core.h"
 #include "core/file_sys/common_funcs.h"
 #include "core/file_sys/content_archive.h"
-#include "core/file_sys/mode.h"
+#include "core/file_sys/fs_filesystem.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
@@ -47,14 +47,14 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro
 
 /**
  * \brief Removes all DLC for a game
- * \param system Raw pointer to the system instance
+ * \param system Reference to the system instance
  * \param program_id Program ID for the game that will have all of its DLC removed
  * \return Number of DLC removed
  */
-inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) {
+inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
     size_t count{};
-    const auto& fs_controller = system->GetFileSystemController();
-    const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
+    const auto& fs_controller = system.GetFileSystemController();
+    const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
         FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
     std::vector<u64> program_dlc_entries;
 
@@ -124,15 +124,15 @@ inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_contro
 
 /**
  * \brief Installs an NSP
- * \param system Raw pointer to the system instance
- * \param vfs Raw pointer to the VfsFilesystem instance in Core::System
+ * \param system Reference to the system instance
+ * \param vfs Reference to the VfsFilesystem instance in Core::System
  * \param filename Path to the NSP file
  * \param callback Callback to report the progress of the installation. The first size_t
  * parameter is the total size of the virtual file and the second is the current progress. If you
  * return true to the callback, it will cancel the installation as soon as possible.
  * \return [InstallResult] representing how the installation finished
  */
-inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vfs,
+inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vfs,
                                 const std::string& filename,
                                 const std::function<bool(size_t, size_t)>& callback) {
     const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
@@ -159,7 +159,7 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf
     };
 
     std::shared_ptr<FileSys::NSP> nsp;
-    FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read);
+    FileSys::VirtualFile file = vfs.OpenFile(filename, FileSys::OpenMode::Read);
     if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) {
         nsp = std::make_shared<FileSys::NSP>(file);
         if (nsp->IsExtractedType()) {
@@ -173,7 +173,7 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf
         return InstallResult::Failure;
     }
     const auto res =
-        system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy);
+        system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy);
     switch (res) {
     case FileSys::InstallResult::Success:
         return InstallResult::Success;
@@ -188,17 +188,17 @@ inline InstallResult InstallNSP(Core::System* system, FileSys::VfsFilesystem* vf
 
 /**
  * \brief Installs an NCA
- * \param vfs Raw pointer to the VfsFilesystem instance in Core::System
+ * \param vfs Reference to the VfsFilesystem instance in Core::System
  * \param filename Path to the NCA file
- * \param registered_cache Raw pointer to the registered cache that the NCA will be installed to
+ * \param registered_cache Reference to the registered cache that the NCA will be installed to
  * \param title_type Type of NCA package to install
  * \param callback Callback to report the progress of the installation. The first size_t
  * parameter is the total size of the virtual file and the second is the current progress. If you
  * return true to the callback, it will cancel the installation as soon as possible.
  * \return [InstallResult] representing how the installation finished
  */
-inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string& filename,
-                                FileSys::RegisteredCache* registered_cache,
+inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string& filename,
+                                FileSys::RegisteredCache& registered_cache,
                                 const FileSys::TitleType title_type,
                                 const std::function<bool(size_t, size_t)>& callback) {
     const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
@@ -224,7 +224,8 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string&
         return true;
     };
 
-    const auto nca = std::make_shared<FileSys::NCA>(vfs->OpenFile(filename, FileSys::Mode::Read));
+    const auto nca =
+        std::make_shared<FileSys::NCA>(vfs.OpenFile(filename, FileSys::OpenMode::Read));
     const auto id = nca->GetStatus();
 
     // Game updates necessary are missing base RomFS
@@ -233,7 +234,7 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string&
         return InstallResult::Failure;
     }
 
-    const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy);
+    const auto res = registered_cache.InstallEntry(*nca, title_type, true, copy);
     if (res == FileSys::InstallResult::Success) {
         return InstallResult::Success;
     } else if (res == FileSys::InstallResult::OverwriteExisting) {
@@ -245,19 +246,19 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem* vfs, const std::string&
 
 /**
  * \brief Verifies the installed contents for a given ManualContentProvider
- * \param system Raw pointer to the system instance
- * \param provider Raw pointer to the content provider that's tracking indexed games
+ * \param system Reference to the system instance
+ * \param provider Reference to the content provider that's tracking indexed games
  * \param callback Callback to report the progress of the installation. The first size_t
  * parameter is the total size of the installed contents and the second is the current progress. If
  * you return true to the callback, it will cancel the installation as soon as possible.
  * \return A list of entries that failed to install. Returns an empty vector if successful.
  */
 inline std::vector<std::string> VerifyInstalledContents(
-    Core::System* system, FileSys::ManualContentProvider* provider,
+    Core::System& system, FileSys::ManualContentProvider& provider,
     const std::function<bool(size_t, size_t)>& callback) {
     // Get content registries.
-    auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
-    auto user_contents = system->GetFileSystemController().GetUserNANDContents();
+    auto bis_contents = system.GetFileSystemController().GetSystemNANDContents();
+    auto user_contents = system.GetFileSystemController().GetUserNANDContents();
 
     std::vector<FileSys::RegisteredCache*> content_providers;
     if (bis_contents) {
@@ -309,11 +310,11 @@ inline std::vector<std::string> VerifyInstalledContents(
             const auto title_id = nca.GetTitleId();
             std::string title_name = "unknown";
 
-            const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
-                                                    FileSys::ContentRecordType::Control);
+            const auto control = provider.GetEntry(FileSys::GetBaseTitleID(title_id),
+                                                   FileSys::ContentRecordType::Control);
             if (control && control->GetStatus() == Loader::ResultStatus::Success) {
-                const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
-                                               *provider};
+                const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+                                               provider};
                 const auto [nacp, logo] = pm.ParseControlNCA(*control);
                 if (nacp) {
                     title_name = nacp->GetApplicationName();
@@ -335,7 +336,7 @@ inline std::vector<std::string> VerifyInstalledContents(
 
 /**
  * \brief Verifies the contents of a given game
- * \param system Raw pointer to the system instance
+ * \param system Reference to the system instance
  * \param game_path Patch to the game file
  * \param callback Callback to report the progress of the installation. The first size_t
  * parameter is the total size of the installed contents and the second is the current progress. If
@@ -343,10 +344,10 @@ inline std::vector<std::string> VerifyInstalledContents(
  * \return GameVerificationResult representing how the verification process finished
  */
 inline GameVerificationResult VerifyGameContents(
-    Core::System* system, const std::string& game_path,
+    Core::System& system, const std::string& game_path,
     const std::function<bool(size_t, size_t)>& callback) {
     const auto loader = Loader::GetLoader(
-        *system, system->GetFilesystem()->OpenFile(game_path, FileSys::Mode::Read));
+        system, system.GetFilesystem()->OpenFile(game_path, FileSys::OpenMode::Read));
     if (loader == nullptr) {
         return GameVerificationResult::NotImplemented;
     }
@@ -368,4 +369,11 @@ inline GameVerificationResult VerifyGameContents(
     return GameVerificationResult::Success;
 }
 
+/**
+ * Checks if the keys required for decrypting firmware and games are available
+ */
+inline bool AreKeysPresent() {
+    return !Core::Crypto::KeyManager::Instance().BaseDeriveNecessary();
+}
+
 } // namespace ContentManager
diff --git a/src/yuzu/applets/qt_error.cpp b/src/yuzu/applets/qt_error.cpp
index 7907ee68b..85bbb114e 100755
--- a/src/yuzu/applets/qt_error.cpp
+++ b/src/yuzu/applets/qt_error.cpp
@@ -25,8 +25,8 @@ void QtErrorDisplay::ShowError(Result error, FinishedCallback finished) const {
     callback = std::move(finished);
     emit MainWindowDisplayError(
         tr("Error Code: %1-%2 (0x%3)")
-            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
-            .arg(error.description, 4, 10, QChar::fromLatin1('0'))
+            .arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+            .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
             .arg(error.raw, 8, 16, QChar::fromLatin1('0')),
         tr("An error has occurred.\nPlease try again or contact the developer of the software."));
 }
@@ -38,8 +38,8 @@ void QtErrorDisplay::ShowErrorWithTimestamp(Result error, std::chrono::seconds t
     const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count());
     emit MainWindowDisplayError(
         tr("Error Code: %1-%2 (0x%3)")
-            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
-            .arg(error.description, 4, 10, QChar::fromLatin1('0'))
+            .arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+            .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
             .arg(error.raw, 8, 16, QChar::fromLatin1('0')),
         tr("An error occurred on %1 at %2.\nPlease try again or contact the developer of the "
            "software.")
@@ -53,8 +53,8 @@ void QtErrorDisplay::ShowCustomErrorText(Result error, std::string dialog_text,
     callback = std::move(finished);
     emit MainWindowDisplayError(
         tr("Error Code: %1-%2 (0x%3)")
-            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
-            .arg(error.description, 4, 10, QChar::fromLatin1('0'))
+            .arg(static_cast<u32>(error.GetModule()) + 2000, 4, 10, QChar::fromLatin1('0'))
+            .arg(error.GetDescription(), 4, 10, QChar::fromLatin1('0'))
             .arg(error.raw, 8, 16, QChar::fromLatin1('0')),
         tr("An error has occurred.\n\n%1\n\n%2")
             .arg(QString::fromStdString(dialog_text))
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 84bc82ae8..201b6b5be 100755
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -11,7 +11,7 @@
 #include <QList>
 
 #include "configuration/shared_widget.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 #include "frontend_common/config.h"
 #include "vk_device_info.h"
 #include "yuzu/configuration/configuration_shared.h"
diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h
index a6e422072..ccfe16c4d 100755
--- a/src/yuzu/configuration/configure_per_game_addons.h
+++ b/src/yuzu/configuration/configure_per_game_addons.h
@@ -8,7 +8,7 @@
 
 #include <QList>
 
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace Core {
 class System;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index a525ffd3f..16c5c2fff 100755
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -17,7 +17,7 @@
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/control_metadata.h"
-#include "core/file_sys/mode.h"
+#include "core/file_sys/fs_filesystem.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
@@ -347,7 +347,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
 
         if (!is_dir &&
             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
-            const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
+            const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read);
             if (!file) {
                 return true;
             }
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2dc3e6886..62ece7369 100755
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -35,8 +35,8 @@
 #include "configuration/configure_per_game.h"
 #include "configuration/configure_tas.h"
 #include "core/file_sys/romfs_factory.h"
-#include "core/file_sys/vfs.h"
-#include "core/file_sys/vfs_real.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_real.h"
 #include "core/frontend/applets/cabinet.h"
 #include "core/frontend/applets/controller.h"
 #include "core/frontend/applets/general_frontend.h"
@@ -56,7 +56,7 @@
 // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
 // defines.
 static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(
-    const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) {
+    const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::OpenMode mode) {
     return vfs->CreateDirectory(path, mode);
 }
 
@@ -423,7 +423,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
     RemoveCachedContents();
 
     // Gen keys if necessary
-    OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
+    OnCheckFirmwareDecryption();
 
     game_list->LoadCompatibilityList();
     game_list->PopulateAsync(UISettings::values.game_dirs);
@@ -1574,8 +1574,6 @@ void GMainWindow::ConnectMenuEvents() {
     connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
 
     // Tools
-    connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
-                                                ReinitializeKeyBehavior::Warning));
     connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
     connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
                  [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
@@ -1882,7 +1880,7 @@ bool GMainWindow::SelectAndSetCurrentUser(
 
 void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
     // Ensure all NCAs are registered before launching the game
-    const auto file = vfs->OpenFile(filepath, FileSys::Mode::Read);
+    const auto file = vfs->OpenFile(filepath, FileSys::OpenMode::Read);
     if (!file) {
         return;
     }
@@ -2276,7 +2274,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
         open_target = tr("Save Data");
         const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
         auto vfs_nand_dir =
-            vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
+            vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read);
 
         if (has_user_save) {
             // User save data
@@ -2501,7 +2499,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
 }
 
 void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) {
-    const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id);
+    const size_t count = ContentManager::RemoveAllDLC(*system, program_id);
     if (count == 0) {
         QMessageBox::warning(this, GetGameListErrorRemoving(type),
                              tr("There are no DLC installed for this title."));
@@ -2655,7 +2653,7 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g
 void GMainWindow::RemoveCacheStorage(u64 program_id) {
     const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
     auto vfs_nand_dir =
-        vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read);
+        vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read);
 
     const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath(
         {}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::CacheStorage,
@@ -2675,7 +2673,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
                                 "cancelled the operation."));
     };
 
-    const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+    const auto loader =
+        Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read));
     if (loader == nullptr) {
         failed();
         return;
@@ -2719,7 +2718,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
     const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed};
     auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false);
 
-    const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
+    const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::OpenMode::ReadWrite);
 
     if (out == nullptr) {
         failed();
@@ -2798,8 +2797,7 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
         return progress.wasCanceled();
     };
 
-    const auto result =
-        ContentManager::VerifyGameContents(system.get(), game_path, QtProgressCallback);
+    const auto result = ContentManager::VerifyGameContents(*system, game_path, QtProgressCallback);
     progress.close();
     switch (result) {
     case ContentManager::GameVerificationResult::Success:
@@ -3018,7 +3016,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
                                        system->GetContentProvider()};
         const auto control = pm.GetControlMetadata();
         const auto loader =
-            Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+            Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read));
         game_title = fmt::format("{:016X}", program_id);
         if (control.first != nullptr) {
             game_title = control.first->GetApplicationName();
@@ -3268,7 +3266,7 @@ void GMainWindow::OnMenuInstallToNAND() {
                 return false;
             };
             future = QtConcurrent::run([this, &file, progress_callback] {
-                return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(),
+                return ContentManager::InstallNSP(*system, *vfs, file.toStdString(),
                                                   progress_callback);
             });
 
@@ -3371,7 +3369,7 @@ ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) {
         }
         return false;
     };
-    return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache,
+    return ContentManager::InstallNCA(*vfs, filename.toStdString(), *registered_cache,
                                       static_cast<FileSys::TitleType>(index), progress_callback);
 }
 
@@ -4121,7 +4119,7 @@ void GMainWindow::OnVerifyInstalledContents() {
     };
 
     const std::vector<std::string> result =
-        ContentManager::VerifyInstalledContents(system.get(), provider.get(), QtProgressCallback);
+        ContentManager::VerifyInstalledContents(*system, *provider, QtProgressCallback);
     progress.close();
 
     if (result.empty()) {
@@ -4551,122 +4549,20 @@ void GMainWindow::OnMouseActivity() {
     }
 }
 
-void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
-    if (behavior == ReinitializeKeyBehavior::Warning) {
-        const auto res = QMessageBox::information(
-            this, tr("Confirm Key Rederivation"),
-            tr("You are about to force rederive all of your keys. \nIf you do not know what "
-               "this "
-               "means or what you are doing, \nthis is a potentially destructive action. "
-               "\nPlease "
-               "make sure this is what you want \nand optionally make backups.\n\nThis will "
-               "delete "
-               "your autogenerated key files and re-run the key derivation module."),
-            QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
-
-        if (res == QMessageBox::Cancel)
-            return;
-
-        const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
-
-        Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated");
-        Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated");
-        Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated");
-    }
-
-    Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
-    bool all_keys_present{true};
-
-    if (keys.BaseDeriveNecessary()) {
-        Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)};
-
-        const auto function = [this, &keys, &pdm] {
-            keys.PopulateFromPartitionData(pdm);
-
-            system->GetFileSystemController().CreateFactories(*vfs);
-            keys.DeriveETicket(pdm, system->GetContentProvider());
-        };
-
-        QString errors;
-        if (!pdm.HasFuses()) {
-            errors += tr("Missing fuses");
-        }
-        if (!pdm.HasBoot0()) {
-            errors += tr(" - Missing BOOT0");
-        }
-        if (!pdm.HasPackage2()) {
-            errors += tr(" - Missing BCPKG2-1-Normal-Main");
-        }
-        if (!pdm.HasProdInfo()) {
-            errors += tr(" - Missing PRODINFO");
-        }
-        if (!errors.isEmpty()) {
-            all_keys_present = false;
-            QMessageBox::warning(
-                this, tr("Derivation Components Missing"),
-                tr("Encryption keys are missing. "
-                   "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu "
-                   "quickstart guide</a> to get all your keys, firmware and "
-                   "games.<br><br><small>(%1)</small>")
-                    .arg(errors));
-        }
-
-        QProgressDialog prog(this);
-        prog.setRange(0, 0);
-        prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
-                             "system's performance."));
-        prog.setWindowTitle(tr("Deriving Keys"));
-
-        prog.show();
-
-        auto future = QtConcurrent::run(function);
-        while (!future.isFinished()) {
-            QCoreApplication::processEvents();
-        }
-
-        prog.close();
-    }
-
+void GMainWindow::OnCheckFirmwareDecryption() {
     system->GetFileSystemController().CreateFactories(*vfs);
-
-    if (all_keys_present && !this->CheckSystemArchiveDecryption()) {
-        LOG_WARNING(Frontend, "Mii model decryption failed");
+    if (!ContentManager::AreKeysPresent()) {
         QMessageBox::warning(
-            this, tr("System Archive Decryption Failed"),
-            tr("Encryption keys failed to decrypt firmware. "
+            this, tr("Derivation Components Missing"),
+            tr("Encryption keys are missing. "
                "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu "
                "quickstart guide</a> to get all your keys, firmware and "
                "games."));
     }
-
     SetFirmwareVersion();
-
-    if (behavior == ReinitializeKeyBehavior::Warning) {
-        game_list->PopulateAsync(UISettings::values.game_dirs);
-    }
-
     UpdateMenuState();
 }
 
-bool GMainWindow::CheckSystemArchiveDecryption() {
-    constexpr u64 MiiModelId = 0x0100000000000802;
-
-    auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
-    if (!bis_system) {
-        // Not having system BIS files is not an error.
-        return true;
-    }
-
-    auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data);
-    if (!mii_nca) {
-        // Not having the Mii model is not an error.
-        return true;
-    }
-
-    // Return whether we are able to decrypt the RomFS of the Mii model.
-    return mii_nca->GetRomFS().get() != nullptr;
-}
-
 bool GMainWindow::CheckFirmwarePresence() {
     constexpr u64 MiiEditId = static_cast<u64>(Service::AM::Applets::AppletProgramId::MiiEdit);
 
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 9a48c219d..106908013 100755
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -125,11 +125,6 @@ enum class EmulatedDirectoryTarget {
     SDMC,
 };
 
-enum class ReinitializeKeyBehavior {
-    NoWarning,
-    Warning,
-};
-
 namespace VkDeviceInfo {
 class Record;
 }
@@ -400,7 +395,7 @@ private slots:
     void OnMiiEdit();
     void OnOpenControllerMenu();
     void OnCaptureScreenshot();
-    void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
+    void OnCheckFirmwareDecryption();
     void OnLanguageChanged(const QString& locale);
     void OnMouseActivity();
     bool OnShutdownBegin();
@@ -441,7 +436,6 @@ private:
     void LoadTranslation();
     void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
     bool CheckDarkMode();
-    bool CheckSystemArchiveDecryption();
     bool CheckFirmwarePresence();
     void SetFirmwareVersion();
     void ConfigureFilesystemProvider(const std::string& filepath);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 77e9bd905..7a5d1e10d 100755
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -224,11 +224,6 @@
     <string>&amp;Stop</string>
    </property>
   </action>
-  <action name="action_Rederive">
-   <property name="text">
-    <string>&amp;Reinitialize keys...</string>
-   </property>
-  </action>
   <action name="action_Verify_installed_contents">
    <property name="text">
     <string>&amp;Verify Installed Contents</string>
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 648771f6c..e062d7d41 100755
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -25,7 +25,7 @@
 #include "core/cpu_manager.h"
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/registered_cache.h"
-#include "core/file_sys/vfs_real.h"
+#include "core/file_sys/vfs/vfs_real.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
 #include "core/telemetry_session.h"