Sfoglia il codice sorgente

Merge pull request #12707 from FearlessTobi/fs-housekeeping

fs: Various cleanups & add path class for later use
liamwhite 2 anni fa
parent
commit
55482ab5dc
100 ha cambiato i file con 2566 aggiunte e 331 eliminazioni
  1. 6 6
      src/android/app/src/main/jni/game_metadata.cpp
  2. 8 7
      src/android/app/src/main/jni/native.cpp
  3. 18 0
      src/common/overflow.h
  4. 89 72
      src/core/CMakeLists.txt
  5. 6 6
      src/core/core.cpp
  6. 1 1
      src/core/core.h
  7. 1 1
      src/core/crypto/aes_util.h
  8. 1 1
      src/core/crypto/encryption_layer.h
  9. 3 3
      src/core/crypto/partition_data_manager.cpp
  10. 1 1
      src/core/crypto/partition_data_manager.h
  11. 2 3
      src/core/file_sys/bis_factory.cpp
  12. 1 1
      src/core/file_sys/bis_factory.h
  13. 2 2
      src/core/file_sys/card_image.cpp
  14. 1 1
      src/core/file_sys/card_image.h
  15. 1 1
      src/core/file_sys/content_archive.cpp
  16. 1 1
      src/core/file_sys/content_archive.h
  17. 1 1
      src/core/file_sys/control_metadata.cpp
  18. 1 1
      src/core/file_sys/control_metadata.h
  19. 0 39
      src/core/file_sys/directory.h
  20. 16 10
      src/core/file_sys/errors.h
  21. 33 0
      src/core/file_sys/fs_directory.h
  22. 65 0
      src/core/file_sys/fs_file.h
  23. 39 0
      src/core/file_sys/fs_filesystem.h
  24. 40 0
      src/core/file_sys/fs_memory_management.h
  25. 22 0
      src/core/file_sys/fs_operate_range.h
  26. 566 0
      src/core/file_sys/fs_path.h
  27. 1239 0
      src/core/file_sys/fs_path_utility.h
  28. 226 0
      src/core/file_sys/fs_string_util.h
  29. 2 2
      src/core/file_sys/fsmitm_romfsbuild.cpp
  30. 1 1
      src/core/file_sys/fsmitm_romfsbuild.h
  31. 1 1
      src/core/file_sys/fssystem/fs_i_storage.h
  32. 1 1
      src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp
  33. 1 1
      src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h
  34. 1 1
      src/core/file_sys/fssystem/fssystem_bucket_tree.h
  35. 1 1
      src/core/file_sys/fssystem/fssystem_compressed_storage.h
  36. 1 1
      src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp
  37. 1 1
      src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h
  38. 1 1
      src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h
  39. 2 2
      src/core/file_sys/fssystem/fssystem_indirect_storage.h
  40. 1 1
      src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h
  41. 2 2
      src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
  42. 1 1
      src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h
  43. 1 1
      src/core/file_sys/fssystem/fssystem_nca_reader.cpp
  44. 1 1
      src/core/file_sys/ips_layer.cpp
  45. 1 1
      src/core/file_sys/ips_layer.h
  46. 1 1
      src/core/file_sys/kernel_executable.cpp
  47. 1 1
      src/core/file_sys/kernel_executable.h
  48. 0 23
      src/core/file_sys/mode.h
  49. 1 1
      src/core/file_sys/nca_metadata.cpp
  50. 1 1
      src/core/file_sys/nca_metadata.h
  51. 1 1
      src/core/file_sys/partition_filesystem.cpp
  52. 1 1
      src/core/file_sys/partition_filesystem.h
  53. 3 3
      src/core/file_sys/patch_manager.cpp
  54. 1 1
      src/core/file_sys/patch_manager.h
  55. 1 1
      src/core/file_sys/program_metadata.cpp
  56. 1 1
      src/core/file_sys/program_metadata.h
  57. 1 1
      src/core/file_sys/registered_cache.cpp
  58. 1 1
      src/core/file_sys/registered_cache.h
  59. 5 5
      src/core/file_sys/romfs.cpp
  60. 1 1
      src/core/file_sys/romfs.h
  61. 1 1
      src/core/file_sys/romfs_factory.h
  62. 1 1
      src/core/file_sys/savedata_factory.cpp
  63. 1 1
      src/core/file_sys/savedata_factory.h
  64. 1 1
      src/core/file_sys/sdmc_factory.cpp
  65. 1 1
      src/core/file_sys/sdmc_factory.h
  66. 1 1
      src/core/file_sys/submission_package.h
  67. 1 1
      src/core/file_sys/system_archive/mii_model.cpp
  68. 1 1
      src/core/file_sys/system_archive/mii_model.h
  69. 1 1
      src/core/file_sys/system_archive/ng_word.cpp
  70. 1 1
      src/core/file_sys/system_archive/ng_word.h
  71. 1 1
      src/core/file_sys/system_archive/shared_font.cpp
  72. 1 1
      src/core/file_sys/system_archive/shared_font.h
  73. 1 1
      src/core/file_sys/system_archive/system_archive.h
  74. 1 1
      src/core/file_sys/system_archive/system_version.cpp
  75. 1 1
      src/core/file_sys/system_archive/system_version.h
  76. 1 1
      src/core/file_sys/system_archive/time_zone_binary.cpp
  77. 1 1
      src/core/file_sys/system_archive/time_zone_binary.h
  78. 14 15
      src/core/file_sys/vfs/vfs.cpp
  79. 6 7
      src/core/file_sys/vfs/vfs.h
  80. 2 2
      src/core/file_sys/vfs/vfs_cached.cpp
  81. 1 1
      src/core/file_sys/vfs/vfs_cached.h
  82. 2 2
      src/core/file_sys/vfs/vfs_concat.cpp
  83. 1 1
      src/core/file_sys/vfs/vfs_concat.h
  84. 1 1
      src/core/file_sys/vfs/vfs_layered.cpp
  85. 1 1
      src/core/file_sys/vfs/vfs_layered.h
  86. 1 1
      src/core/file_sys/vfs/vfs_offset.cpp
  87. 1 1
      src/core/file_sys/vfs/vfs_offset.h
  88. 27 28
      src/core/file_sys/vfs/vfs_real.cpp
  89. 15 12
      src/core/file_sys/vfs/vfs_real.h
  90. 1 1
      src/core/file_sys/vfs/vfs_static.h
  91. 0 0
      src/core/file_sys/vfs/vfs_types.h
  92. 1 1
      src/core/file_sys/vfs/vfs_vector.cpp
  93. 1 1
      src/core/file_sys/vfs/vfs_vector.h
  94. 1 1
      src/core/file_sys/xts_archive.cpp
  95. 1 1
      src/core/file_sys/xts_archive.h
  96. 3 3
      src/core/frontend/applets/error.cpp
  97. 32 6
      src/core/hle/result.h
  98. 2 2
      src/core/hle/service/am/applets/applet_error.cpp
  99. 4 4
      src/core/hle/service/am/applets/applet_web_browser.cpp
  100. 1 1
      src/core/hle/service/am/applets/applet_web_browser.h

+ 6 - 6
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;
     }

+ 8 - 7
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;
     }
@@ -475,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")) {
@@ -719,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));
@@ -889,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,

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

+ 89 - 72
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
@@ -486,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
@@ -551,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
@@ -565,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
@@ -586,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
@@ -759,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
@@ -838,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
@@ -859,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

+ 6 - 6
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 {

+ 1 - 1
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;

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 3 - 3
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;

+ 1 - 1
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 {
 

+ 2 - 3
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) {

+ 1 - 1
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 {
 

+ 2 - 2
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 {

+ 1 - 1
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;

+ 1 - 1
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"

+ 1 - 1
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;

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 0 - 39
src/core/file_sys/directory.h

@@ -1,39 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <cstddef>
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// FileSys namespace
-
-namespace FileSys {
-
-enum class EntryType : u8 {
-    Directory = 0,
-    File = 1,
-};
-
-// Structure of a directory entry, from
-// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
-struct Entry {
-    Entry(std::string_view view, EntryType entry_type, u64 entry_size)
-        : type{entry_type}, file_size{entry_size} {
-        const std::size_t copy_size = view.copy(filename, std::size(filename) - 1);
-        filename[copy_size] = '\0';
-    }
-
-    char filename[0x301];
-    INSERT_PADDING_BYTES(3);
-    EntryType type;
-    INSERT_PADDING_BYTES(3);
-    u64 file_size;
-};
-static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!");
-static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
-static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
-
-} // namespace FileSys

+ 16 - 10
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};

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

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

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

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

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

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

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

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

+ 2 - 2
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 2 - 2
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 {
 

+ 1 - 1
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 {
 

+ 2 - 2
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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;

+ 0 - 23
src/core/file_sys/mode.h

@@ -1,23 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 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 Mode : u32 {
-    Read = 1 << 0,
-    Write = 1 << 1,
-    ReadWrite = Read | Write,
-    Append = 1 << 2,
-    ReadAppend = Read | Append,
-    WriteAppend = Write | Append,
-    All = ReadWrite | Append,
-};
-
-DECLARE_ENUM_FLAG_OPERATORS(Mode)
-
-} // namespace FileSys

+ 1 - 1
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 {
 

+ 1 - 1
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;

+ 1 - 1
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 {

+ 1 - 1
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;

+ 3 - 3
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"

+ 1 - 1
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 {

+ 1 - 1
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 {

+ 1 - 1
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;

+ 1 - 1
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 {

+ 1 - 1
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;

+ 5 - 5
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 {

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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 {

+ 1 - 1
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 {

+ 1 - 1
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;

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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 {
 

+ 1 - 1
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 {
 

+ 1 - 1
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 {

+ 1 - 1
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 {
 

+ 1 - 1
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"
 

+ 1 - 1
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 {
 

+ 14 - 15
src/core/file_sys/vfs.cpp → src/core/file_sys/vfs/vfs.cpp

@@ -5,8 +5,7 @@
 #include <numeric>
 #include <string>
 #include "common/fs/path_util.h"
-#include "core/file_sys/mode.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 
@@ -36,12 +35,12 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
     return VfsEntryType::None;
 }
 
-VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+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_, Mode perms) {
+VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
     const auto path = Common::FS::SanitizePath(path_);
     return root->CreateFileRelative(path);
 }
@@ -54,17 +53,17 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
     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, Mode::ReadWrite);
+        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, Mode::Read);
+    const auto old_file = OpenFile(old_path, OpenMode::Read);
     if (old_file == nullptr)
         return nullptr;
-    auto new_file = OpenFile(new_path, Mode::Read);
+    auto new_file = OpenFile(new_path, OpenMode::Read);
     if (new_file != nullptr)
         return nullptr;
-    new_file = CreateFile(new_path, Mode::Write);
+    new_file = CreateFile(new_path, OpenMode::Write);
     if (new_file == nullptr)
         return nullptr;
     if (!VfsRawCopy(old_file, new_file))
@@ -87,18 +86,18 @@ VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view
 
 bool VfsFilesystem::DeleteFile(std::string_view path_) {
     const auto path = Common::FS::SanitizePath(path_);
-    auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
+    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_, Mode perms) {
+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_, Mode perms) {
+VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
     const auto path = Common::FS::SanitizePath(path_);
     return root->CreateDirectoryRelative(path);
 }
@@ -108,13 +107,13 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
     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, Mode::Read);
+    auto old_dir = OpenDirectory(old_path, OpenMode::Read);
     if (old_dir == nullptr)
         return nullptr;
-    auto new_dir = OpenDirectory(new_path, Mode::Read);
+    auto new_dir = OpenDirectory(new_path, OpenMode::Read);
     if (new_dir != nullptr)
         return nullptr;
-    new_dir = CreateDirectory(new_path, Mode::Write);
+    new_dir = CreateDirectory(new_path, OpenMode::Write);
     if (new_dir == nullptr)
         return nullptr;
 
@@ -149,7 +148,7 @@ VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_v
 
 bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
     const auto path = Common::FS::SanitizePath(path_);
-    auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
+    auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write);
     if (parent == nullptr)
         return false;
     return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));

+ 6 - 7
src/core/file_sys/vfs.h → src/core/file_sys/vfs/vfs.h

@@ -13,12 +13,11 @@
 
 #include "common/common_funcs.h"
 #include "common/common_types.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys {
 
-enum class Mode : u32;
-
 // An enumeration representing what can be at the end of a path in a VfsFilesystem
 enum class VfsEntryType {
     None,
@@ -49,9 +48,9 @@ public:
     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, Mode perms);
+    virtual VirtualFile OpenFile(std::string_view path, OpenMode perms);
     // Creates a new, empty file at path
-    virtual VirtualFile CreateFile(std::string_view path, Mode perms);
+    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);
@@ -62,9 +61,9 @@ public:
     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, Mode perms);
+    virtual VirtualDir OpenDirectory(std::string_view path, OpenMode perms);
     // Creates a new, empty directory at path
-    virtual VirtualDir CreateDirectory(std::string_view path, Mode perms);
+    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);

+ 2 - 2
src/core/file_sys/vfs_cached.cpp → src/core/file_sys/vfs/vfs_cached.cpp

@@ -1,8 +1,8 @@
 // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include "core/file_sys/vfs_cached.h"
-#include "core/file_sys/vfs_types.h"
+#include "core/file_sys/vfs/vfs_cached.h"
+#include "core/file_sys/vfs/vfs_types.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_cached.h → src/core/file_sys/vfs/vfs_cached.h

@@ -5,7 +5,7 @@
 
 #include <string_view>
 #include <vector>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 2 - 2
src/core/file_sys/vfs_concat.cpp → src/core/file_sys/vfs/vfs_concat.cpp

@@ -5,8 +5,8 @@
 #include <utility>
 
 #include "common/assert.h"
-#include "core/file_sys/vfs_concat.h"
-#include "core/file_sys/vfs_static.h"
+#include "core/file_sys/vfs/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_static.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_concat.h → src/core/file_sys/vfs/vfs_concat.h

@@ -6,7 +6,7 @@
 #include <compare>
 #include <map>
 #include <memory>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_layered.cpp → src/core/file_sys/vfs/vfs_layered.cpp

@@ -5,7 +5,7 @@
 #include <set>
 #include <unordered_set>
 #include <utility>
-#include "core/file_sys/vfs_layered.h"
+#include "core/file_sys/vfs/vfs_layered.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_layered.h → src/core/file_sys/vfs/vfs_layered.h

@@ -4,7 +4,7 @@
 #pragma once
 
 #include <memory>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_offset.cpp → src/core/file_sys/vfs/vfs_offset.cpp

@@ -4,7 +4,7 @@
 #include <algorithm>
 #include <utility>
 
-#include "core/file_sys/vfs_offset.h"
+#include "core/file_sys/vfs/vfs_offset.h"
 
 namespace FileSys {
 

+ 1 - 1
src/core/file_sys/vfs_offset.h → src/core/file_sys/vfs/vfs_offset.h

@@ -5,7 +5,7 @@
 
 #include <memory>
 
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 27 - 28
src/core/file_sys/vfs_real.cpp → src/core/file_sys/vfs/vfs_real.cpp

@@ -10,8 +10,8 @@
 #include "common/fs/fs.h"
 #include "common/fs/path_util.h"
 #include "common/logging/log.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"
 
 // For FileTimeStampRaw
 #include <sys/stat.h>
@@ -28,16 +28,14 @@ namespace {
 
 constexpr size_t MaxOpenFiles = 512;
 
-constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
+constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
     switch (mode) {
-    case Mode::Read:
+    case OpenMode::Read:
         return FS::FileAccessMode::Read;
-    case Mode::Write:
-    case Mode::ReadWrite:
-    case Mode::Append:
-    case Mode::ReadAppend:
-    case Mode::WriteAppend:
-    case Mode::All:
+    case OpenMode::Write:
+    case OpenMode::ReadWrite:
+    case OpenMode::AllowAppend:
+    case OpenMode::All:
         return FS::FileAccessMode::ReadWrite;
     default:
         return {};
@@ -74,7 +72,7 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
 }
 
 VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
-                                                 Mode perms) {
+                                                 OpenMode perms) {
     const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
     std::scoped_lock lk{list_lock};
 
@@ -98,11 +96,11 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
     return file;
 }
 
-VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
     return OpenFileFromEntry(path_, {}, perms);
 }
 
-VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode 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};
@@ -145,7 +143,7 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
     if (!FS::RenameFile(old_path, new_path)) {
         return nullptr;
     }
-    return OpenFile(new_path, Mode::ReadWrite);
+    return OpenFile(new_path, OpenMode::ReadWrite);
 }
 
 bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
@@ -157,12 +155,12 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
     return FS::RemoveFile(path);
 }
 
-VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+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_, Mode 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;
@@ -184,7 +182,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
     if (!FS::RenameDir(old_path, new_path)) {
         return nullptr;
     }
-    return OpenDirectory(new_path, Mode::ReadWrite);
+    return OpenDirectory(new_path, OpenMode::ReadWrite);
 }
 
 bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
@@ -193,7 +191,7 @@ bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
 }
 
 std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::string& path,
-                                                                 Mode perms,
+                                                                 OpenMode perms,
                                                                  FileReference& reference) {
     std::unique_lock lk{list_lock};
 
@@ -266,7 +264,7 @@ void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference)
 }
 
 RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
-                         const std::string& path_, Mode perms_, std::optional<u64> size_)
+                         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_) {}
@@ -298,11 +296,11 @@ VirtualDir RealVfsFile::GetContainingDirectory() const {
 }
 
 bool RealVfsFile::IsWritable() const {
-    return True(perms & Mode::Write);
+    return True(perms & OpenMode::Write);
 }
 
 bool RealVfsFile::IsReadable() const {
-    return True(perms & Mode::Read);
+    return True(perms & OpenMode::Read);
 }
 
 std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
@@ -331,7 +329,7 @@ bool RealVfsFile::Rename(std::string_view name) {
 
 template <>
 std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
-    if (perms == Mode::Append) {
+    if (perms == OpenMode::AllowAppend) {
         return {};
     }
 
@@ -353,7 +351,7 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
 
 template <>
 std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
-    if (perms == Mode::Append) {
+    if (perms == OpenMode::AllowAppend) {
         return {};
     }
 
@@ -373,10 +371,11 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
     return out;
 }
 
-RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
+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 & Mode::Write)) {
+    if (!FS::Exists(path) && True(perms & OpenMode::Write)) {
         void(FS::CreateDirs(path));
     }
 }
@@ -456,11 +455,11 @@ std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
 }
 
 bool RealVfsDirectory::IsWritable() const {
-    return True(perms & Mode::Write);
+    return True(perms & OpenMode::Write);
 }
 
 bool RealVfsDirectory::IsReadable() const {
-    return True(perms & Mode::Read);
+    return True(perms & OpenMode::Read);
 }
 
 std::string RealVfsDirectory::GetName() const {
@@ -507,7 +506,7 @@ std::string RealVfsDirectory::GetFullPath() const {
 }
 
 std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
-    if (perms == Mode::Append) {
+    if (perms == OpenMode::AllowAppend) {
         return {};
     }
 

+ 15 - 12
src/core/file_sys/vfs_real.h → src/core/file_sys/vfs/vfs_real.h

@@ -8,8 +8,8 @@
 #include <optional>
 #include <string_view>
 #include "common/intrusive_list.h"
-#include "core/file_sys/mode.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/fs_filesystem.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace Common::FS {
 class IOFile;
@@ -33,13 +33,14 @@ public:
     bool IsReadable() const override;
     bool IsWritable() const override;
     VfsEntryType GetEntryType(std::string_view path) const override;
-    VirtualFile OpenFile(std::string_view path, Mode perms = Mode::Read) override;
-    VirtualFile CreateFile(std::string_view path, Mode perms = Mode::ReadWrite) 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, Mode perms = Mode::Read) override;
-    VirtualDir CreateDirectory(std::string_view path, Mode perms = Mode::ReadWrite) 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;
@@ -54,14 +55,14 @@ private:
 
 private:
     friend class RealVfsFile;
-    std::unique_lock<std::mutex> RefreshReference(const std::string& path, Mode perms,
+    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,
-                                  Mode perms = Mode::Read);
+                                  OpenMode perms = OpenMode::Read);
 
 private:
     void EvictSingleReferenceLocked();
@@ -89,7 +90,8 @@ public:
 
 private:
     RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
-                const std::string& path, Mode perms = Mode::Read, std::optional<u64> size = {});
+                const std::string& path, OpenMode perms = OpenMode::Read,
+                std::optional<u64> size = {});
 
     RealVfsFilesystem& base;
     std::unique_ptr<FileReference> reference;
@@ -97,7 +99,7 @@ private:
     std::string parent_path;
     std::vector<std::string> path_components;
     std::optional<u64> size;
-    Mode perms;
+    OpenMode perms;
 };
 
 // An implementation of VfsDirectory that represents a directory on the user's computer.
@@ -130,7 +132,8 @@ public:
     std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
 
 private:
-    RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
+    RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
+                     OpenMode perms = OpenMode::Read);
 
     template <typename T, typename R>
     std::vector<std::shared_ptr<R>> IterateEntries() const;
@@ -139,7 +142,7 @@ private:
     std::string path;
     std::string parent_path;
     std::vector<std::string> path_components;
-    Mode perms;
+    OpenMode perms;
 };
 
 } // namespace FileSys

+ 1 - 1
src/core/file_sys/vfs_static.h → src/core/file_sys/vfs/vfs_static.h

@@ -7,7 +7,7 @@
 #include <memory>
 #include <string_view>
 
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 0 - 0
src/core/file_sys/vfs_types.h → src/core/file_sys/vfs/vfs_types.h


+ 1 - 1
src/core/file_sys/vfs_vector.cpp → src/core/file_sys/vfs/vfs_vector.cpp

@@ -3,7 +3,7 @@
 
 #include <algorithm>
 #include <utility>
-#include "core/file_sys/vfs_vector.h"
+#include "core/file_sys/vfs/vfs_vector.h"
 
 namespace FileSys {
 VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name_, VirtualDir parent_)

+ 1 - 1
src/core/file_sys/vfs_vector.h → src/core/file_sys/vfs/vfs_vector.h

@@ -8,7 +8,7 @@
 #include <memory>
 #include <string>
 #include <vector>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs/vfs.h"
 
 namespace FileSys {
 

+ 1 - 1
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"
 

+ 1 - 1
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;

+ 3 - 3
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);
 }

+ 32 - 6
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.

+ 2 - 2
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()},
         };
     }
 

+ 4 - 4
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);
 }

+ 1 - 1
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"

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