summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/overflow.h17
-rw-r--r--src/core/CMakeLists.txt41
-rw-r--r--src/core/file_sys/errors.h11
-rw-r--r--src/core/file_sys/fs_file.h63
-rw-r--r--src/core/file_sys/fs_memory_management.h48
-rw-r--r--src/core/file_sys/fs_operate_range.h20
-rw-r--r--src/core/file_sys/fs_path.h570
-rw-r--r--src/core/file_sys/fs_path_utility.h1240
-rw-r--r--src/core/file_sys/fs_string_util.h241
-rw-r--r--src/core/hle/service/filesystem/fsp/fs_i_directory.cpp1
-rw-r--r--src/core/hle/service/filesystem/fsp/fs_i_directory.h6
11 files changed, 2228 insertions, 30 deletions
diff --git a/src/common/overflow.h b/src/common/overflow.h
index 44d8e7e73..e765c0d89 100644
--- a/src/common/overflow.h
+++ b/src/common/overflow.h
@@ -19,4 +19,21 @@ inline T WrappingAdd(T lhs, T rhs) {
19 return BitCast<T>(lhs_u + rhs_u); 19 return BitCast<T>(lhs_u + rhs_u);
20} 20}
21 21
22template <typename T>
23 requires(std::is_integral_v<T> && std::is_signed_v<T>)
24inline bool CanAddWithoutOverflow(T lhs, T rhs) {
25#ifdef _MSC_VER
26 if (lhs >= 0 && rhs >= 0) {
27 return WrappingAdd(lhs, rhs) >= std::max(lhs, rhs);
28 } else if (lhs < 0 && rhs < 0) {
29 return WrappingAdd(lhs, rhs) <= std::min(lhs, rhs);
30 } else {
31 return true;
32 }
33#else
34 T res;
35 return !__builtin_add_overflow(lhs, rhs, &res);
36#endif
37}
38
22} // namespace Common 39} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d71c2cead..347bbf2d0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -58,7 +58,7 @@ add_library(core STATIC
58 file_sys/fs_operate_range.h 58 file_sys/fs_operate_range.h
59 file_sys/fs_path.h 59 file_sys/fs_path.h
60 file_sys/fs_path_utility.h 60 file_sys/fs_path_utility.h
61 file_sys/fs_util_character_encoding.h 61 file_sys/fs_string_util.h
62 file_sys/fsmitm_romfsbuild.cpp 62 file_sys/fsmitm_romfsbuild.cpp
63 file_sys/fsmitm_romfsbuild.h 63 file_sys/fsmitm_romfsbuild.h
64 file_sys/fssystem/fs_i_storage.h 64 file_sys/fssystem/fs_i_storage.h
@@ -104,25 +104,10 @@ add_library(core STATIC
104 file_sys/fssystem/fssystem_switch_storage.h 104 file_sys/fssystem/fssystem_switch_storage.h
105 file_sys/fssystem/fssystem_utility.cpp 105 file_sys/fssystem/fssystem_utility.cpp
106 file_sys/fssystem/fssystem_utility.h 106 file_sys/fssystem/fssystem_utility.h
107 file_sys/fssystem/fs_types.h
108 file_sys/bis_factory.cpp
109 file_sys/bis_factory.h
110 file_sys/card_image.cpp
111 file_sys/card_image.h
112 file_sys/common_funcs.h
113 file_sys/content_archive.cpp
114 file_sys/content_archive.h
115 file_sys/control_metadata.cpp
116 file_sys/control_metadata.h
117 file_sys/directory.h
118 file_sys/errors.h
119 file_sys/fsmitm_romfsbuild.cpp
120 file_sys/fsmitm_romfsbuild.h
121 file_sys/ips_layer.cpp 107 file_sys/ips_layer.cpp
122 file_sys/ips_layer.h 108 file_sys/ips_layer.h
123 file_sys/kernel_executable.cpp 109 file_sys/kernel_executable.cpp
124 file_sys/kernel_executable.h 110 file_sys/kernel_executable.h
125 file_sys/mode.h
126 file_sys/nca_metadata.cpp 111 file_sys/nca_metadata.cpp
127 file_sys/nca_metadata.h 112 file_sys/nca_metadata.h
128 file_sys/partition_filesystem.cpp 113 file_sys/partition_filesystem.cpp
@@ -215,7 +200,6 @@ add_library(core STATIC
215 hle/kernel/board/nintendo/nx/secure_monitor.h 200 hle/kernel/board/nintendo/nx/secure_monitor.h
216 hle/kernel/code_set.cpp 201 hle/kernel/code_set.cpp
217 hle/kernel/code_set.h 202 hle/kernel/code_set.h
218 hle/kernel/svc_results.h
219 hle/kernel/global_scheduler_context.cpp 203 hle/kernel/global_scheduler_context.cpp
220 hle/kernel/global_scheduler_context.h 204 hle/kernel/global_scheduler_context.h
221 hle/kernel/init/init_slab_setup.cpp 205 hle/kernel/init/init_slab_setup.cpp
@@ -225,11 +209,11 @@ add_library(core STATIC
225 hle/kernel/k_address_arbiter.h 209 hle/kernel/k_address_arbiter.h
226 hle/kernel/k_address_space_info.cpp 210 hle/kernel/k_address_space_info.cpp
227 hle/kernel/k_address_space_info.h 211 hle/kernel/k_address_space_info.h
212 hle/kernel/k_affinity_mask.h
228 hle/kernel/k_auto_object.cpp 213 hle/kernel/k_auto_object.cpp
229 hle/kernel/k_auto_object.h 214 hle/kernel/k_auto_object.h
230 hle/kernel/k_auto_object_container.cpp 215 hle/kernel/k_auto_object_container.cpp
231 hle/kernel/k_auto_object_container.h 216 hle/kernel/k_auto_object_container.h
232 hle/kernel/k_affinity_mask.h
233 hle/kernel/k_capabilities.cpp 217 hle/kernel/k_capabilities.cpp
234 hle/kernel/k_capabilities.h 218 hle/kernel/k_capabilities.h
235 hle/kernel/k_class_token.cpp 219 hle/kernel/k_class_token.cpp
@@ -253,9 +237,9 @@ add_library(core STATIC
253 hle/kernel/k_event_info.h 237 hle/kernel/k_event_info.h
254 hle/kernel/k_handle_table.cpp 238 hle/kernel/k_handle_table.cpp
255 hle/kernel/k_handle_table.h 239 hle/kernel/k_handle_table.h
256 hle/kernel/k_hardware_timer_base.h
257 hle/kernel/k_hardware_timer.cpp 240 hle/kernel/k_hardware_timer.cpp
258 hle/kernel/k_hardware_timer.h 241 hle/kernel/k_hardware_timer.h
242 hle/kernel/k_hardware_timer_base.h
259 hle/kernel/k_interrupt_manager.cpp 243 hle/kernel/k_interrupt_manager.cpp
260 hle/kernel/k_interrupt_manager.h 244 hle/kernel/k_interrupt_manager.h
261 hle/kernel/k_light_client_session.cpp 245 hle/kernel/k_light_client_session.cpp
@@ -282,10 +266,10 @@ add_library(core STATIC
282 hle/kernel/k_page_bitmap.h 266 hle/kernel/k_page_bitmap.h
283 hle/kernel/k_page_buffer.cpp 267 hle/kernel/k_page_buffer.cpp
284 hle/kernel/k_page_buffer.h 268 hle/kernel/k_page_buffer.h
285 hle/kernel/k_page_heap.cpp
286 hle/kernel/k_page_heap.h
287 hle/kernel/k_page_group.cpp 269 hle/kernel/k_page_group.cpp
288 hle/kernel/k_page_group.h 270 hle/kernel/k_page_group.h
271 hle/kernel/k_page_heap.cpp
272 hle/kernel/k_page_heap.h
289 hle/kernel/k_page_table.h 273 hle/kernel/k_page_table.h
290 hle/kernel/k_page_table_base.cpp 274 hle/kernel/k_page_table_base.cpp
291 hle/kernel/k_page_table_base.h 275 hle/kernel/k_page_table_base.h
@@ -350,8 +334,6 @@ add_library(core STATIC
350 hle/kernel/slab_helpers.h 334 hle/kernel/slab_helpers.h
351 hle/kernel/svc.cpp 335 hle/kernel/svc.cpp
352 hle/kernel/svc.h 336 hle/kernel/svc.h
353 hle/kernel/svc_common.h
354 hle/kernel/svc_types.h
355 hle/kernel/svc/svc_activity.cpp 337 hle/kernel/svc/svc_activity.cpp
356 hle/kernel/svc/svc_address_arbiter.cpp 338 hle/kernel/svc/svc_address_arbiter.cpp
357 hle/kernel/svc/svc_address_translation.cpp 339 hle/kernel/svc/svc_address_translation.cpp
@@ -389,6 +371,9 @@ add_library(core STATIC
389 hle/kernel/svc/svc_thread_profiler.cpp 371 hle/kernel/svc/svc_thread_profiler.cpp
390 hle/kernel/svc/svc_tick.cpp 372 hle/kernel/svc/svc_tick.cpp
391 hle/kernel/svc/svc_transfer_memory.cpp 373 hle/kernel/svc/svc_transfer_memory.cpp
374 hle/kernel/svc_common.h
375 hle/kernel/svc_results.h
376 hle/kernel/svc_types.h
392 hle/result.h 377 hle/result.h
393 hle/service/acc/acc.cpp 378 hle/service/acc/acc.cpp
394 hle/service/acc/acc.h 379 hle/service/acc/acc.h
@@ -519,17 +504,17 @@ add_library(core STATIC
519 hle/service/filesystem/fsp/fs_i_filesystem.h 504 hle/service/filesystem/fsp/fs_i_filesystem.h
520 hle/service/filesystem/fsp/fs_i_storage.cpp 505 hle/service/filesystem/fsp/fs_i_storage.cpp
521 hle/service/filesystem/fsp/fs_i_storage.h 506 hle/service/filesystem/fsp/fs_i_storage.h
507 hle/service/filesystem/fsp/fsp_ldr.cpp
508 hle/service/filesystem/fsp/fsp_ldr.h
509 hle/service/filesystem/fsp/fsp_pr.cpp
510 hle/service/filesystem/fsp/fsp_pr.h
522 hle/service/filesystem/fsp/fsp_srv.cpp 511 hle/service/filesystem/fsp/fsp_srv.cpp
523 hle/service/filesystem/fsp/fsp_srv.h 512 hle/service/filesystem/fsp/fsp_srv.h
524 hle/service/filesystem/fsp_ldr.cpp 513 hle/service/filesystem/fsp/fsp_util.h
525 hle/service/filesystem/fsp_ldr.h
526 hle/service/filesystem/fsp_pr.cpp
527 hle/service/filesystem/fsp_pr.h
528 hle/service/filesystem/romfs_controller.cpp 514 hle/service/filesystem/romfs_controller.cpp
529 hle/service/filesystem/romfs_controller.h 515 hle/service/filesystem/romfs_controller.h
530 hle/service/filesystem/save_data_controller.cpp 516 hle/service/filesystem/save_data_controller.cpp
531 hle/service/filesystem/save_data_controller.h 517 hle/service/filesystem/save_data_controller.h
532 hle/service/filesystem/fsp_util.h
533 hle/service/fgm/fgm.cpp 518 hle/service/fgm/fgm.cpp
534 hle/service/fgm/fgm.h 519 hle/service/fgm/fgm.h
535 hle/service/friend/friend.cpp 520 hle/service/friend/friend.cpp
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 7134445cf..d4e0eb6f4 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -73,10 +73,21 @@ constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324};
73constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; 73constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325};
74constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; 74constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326};
75constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; 75constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327};
76constexpr Result ResultUnexpectedInPathA{ErrorModule::FS, 5328};
76constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; 77constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001};
78constexpr Result ResultInvalidPath{ErrorModule::FS, 6002};
79constexpr Result ResultTooLongPath{ErrorModule::FS, 6003};
80constexpr Result ResultInvalidCharacter{ErrorModule::FS, 6004};
81constexpr Result ResultInvalidPathFormat{ErrorModule::FS, 6005};
82constexpr Result ResultDirectoryUnobtainable{ErrorModule::FS, 6006};
83constexpr Result ResultNotNormalized{ErrorModule::FS, 6007};
77constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; 84constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061};
78constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; 85constexpr Result ResultInvalidSize{ErrorModule::FS, 6062};
79constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; 86constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063};
87constexpr Result ResultInvalidOpenMode{ErrorModule::FS, 6072};
88constexpr Result ResultFileExtensionWithoutOpenModeAllowAppend{ErrorModule::FS, 6201};
89constexpr Result ResultReadNotPermitted{ErrorModule::FS, 6202};
90constexpr Result ResultWriteNotPermitted{ErrorModule::FS, 6203};
80constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; 91constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325};
81constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; 92constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387};
82constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; 93constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388};
diff --git a/src/core/file_sys/fs_file.h b/src/core/file_sys/fs_file.h
new file mode 100644
index 000000000..35f66b959
--- /dev/null
+++ b/src/core/file_sys/fs_file.h
@@ -0,0 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6namespace FileSys {
7
8struct ReadOption {
9 u32 _value;
10
11 static const ReadOption None;
12};
13
14enum ReadOptionFlag : u32 {
15 ReadOptionFlag_None = (0 << 0),
16};
17
18inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None};
19
20inline constexpr bool operator==(const ReadOption& lhs, const ReadOption& rhs) {
21 return lhs._value == rhs._value;
22}
23
24inline constexpr bool operator!=(const ReadOption& lhs, const ReadOption& rhs) {
25 return !(lhs == rhs);
26}
27
28static_assert(sizeof(ReadOption) == sizeof(u32));
29
30enum WriteOptionFlag : u32 {
31 WriteOptionFlag_None = (0 << 0),
32 WriteOptionFlag_Flush = (1 << 0),
33};
34
35struct WriteOption {
36 u32 _value;
37
38 constexpr inline bool HasFlushFlag() const {
39 return _value & WriteOptionFlag_Flush;
40 }
41
42 static const WriteOption None;
43 static const WriteOption Flush;
44};
45
46inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None};
47inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush};
48
49inline constexpr bool operator==(const WriteOption& lhs, const WriteOption& rhs) {
50 return lhs._value == rhs._value;
51}
52
53inline constexpr bool operator!=(const WriteOption& lhs, const WriteOption& rhs) {
54 return !(lhs == rhs);
55}
56
57static_assert(sizeof(WriteOption) == sizeof(u32));
58
59struct FileHandle {
60 void* handle;
61};
62
63} // namespace FileSys
diff --git a/src/core/file_sys/fs_memory_management.h b/src/core/file_sys/fs_memory_management.h
new file mode 100644
index 000000000..596143ba9
--- /dev/null
+++ b/src/core/file_sys/fs_memory_management.h
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include "common/alignment.h"
8
9namespace FileSys {
10
11std::mutex g_mutex;
12
13constexpr size_t RequiredAlignment = alignof(u64);
14
15void* AllocateUnsafe(size_t size) {
16 /* Allocate. */
17 void* const ptr = ::operator new(size, std::align_val_t{RequiredAlignment});
18
19 /* Check alignment. */
20 ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment));
21
22 /* Return allocated pointer. */
23 return ptr;
24}
25
26void DeallocateUnsafe(void* ptr, size_t size) {
27 /* Deallocate the pointer. */
28 ::operator delete(ptr, std::align_val_t{RequiredAlignment});
29}
30
31void* Allocate(size_t size) {
32 /* Lock the allocator. */
33 std::scoped_lock lk(g_mutex);
34
35 return AllocateUnsafe(size);
36}
37
38void Deallocate(void* ptr, size_t size) {
39 /* If the pointer is non-null, deallocate it. */
40 if (ptr != nullptr) {
41 /* Lock the allocator. */
42 std::scoped_lock lk(g_mutex);
43
44 DeallocateUnsafe(ptr, size);
45 }
46}
47
48} // namespace FileSys
diff --git a/src/core/file_sys/fs_operate_range.h b/src/core/file_sys/fs_operate_range.h
new file mode 100644
index 000000000..750999907
--- /dev/null
+++ b/src/core/file_sys/fs_operate_range.h
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6namespace FileSys {
7
8enum class OperationId : s64 {
9 FillZero = 0,
10 DestroySignature = 1,
11 Invalidate = 2,
12 QueryRange = 3,
13 QueryUnpreparedRange = 4,
14 QueryLazyLoadCompletionRate = 5,
15 SetLazyLoadPriority = 6,
16
17 ReadLazyLoadFileForciblyForDebug = 10001,
18};
19
20} // namespace FileSys
diff --git a/src/core/file_sys/fs_path.h b/src/core/file_sys/fs_path.h
new file mode 100644
index 000000000..9ea452644
--- /dev/null
+++ b/src/core/file_sys/fs_path.h
@@ -0,0 +1,570 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/alignment.h"
7#include "common/common_funcs.h"
8#include "core/file_sys/errors.h"
9#include "core/file_sys/fs_memory_management.h"
10#include "core/file_sys/fs_path_utility.h"
11#include "core/file_sys/fs_string_util.h"
12#include "core/hle/result.h"
13
14namespace FileSys {
15class DirectoryPathParser;
16
17class Path {
18 YUZU_NON_COPYABLE(Path);
19 YUZU_NON_MOVEABLE(Path);
20
21private:
22 static constexpr const char* EmptyPath = "";
23 static constexpr size_t WriteBufferAlignmentLength = 8;
24
25private:
26 friend class DirectoryPathParser;
27
28public:
29 class WriteBuffer {
30 YUZU_NON_COPYABLE(WriteBuffer);
31
32 private:
33 char* m_buffer;
34 size_t m_length_and_is_normalized;
35
36 public:
37 constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) { /* ... */
38 }
39
40 constexpr ~WriteBuffer() {
41 if (m_buffer != nullptr) {
42 Deallocate(m_buffer, this->GetLength());
43 this->ResetBuffer();
44 }
45 }
46
47 constexpr WriteBuffer(WriteBuffer&& rhs)
48 : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
49 rhs.ResetBuffer();
50 }
51
52 constexpr WriteBuffer& operator=(WriteBuffer&& rhs) {
53 if (m_buffer != nullptr) {
54 Deallocate(m_buffer, this->GetLength());
55 }
56
57 m_buffer = rhs.m_buffer;
58 m_length_and_is_normalized = rhs.m_length_and_is_normalized;
59
60 rhs.ResetBuffer();
61
62 return *this;
63 }
64
65 constexpr void ResetBuffer() {
66 m_buffer = nullptr;
67 this->SetLength(0);
68 }
69
70 constexpr char* Get() const {
71 return m_buffer;
72 }
73
74 constexpr size_t GetLength() const {
75 return m_length_and_is_normalized >> 1;
76 }
77
78 constexpr bool IsNormalized() const {
79 return static_cast<bool>(m_length_and_is_normalized & 1);
80 }
81
82 constexpr void SetNormalized() {
83 m_length_and_is_normalized |= static_cast<size_t>(1);
84 }
85
86 constexpr void SetNotNormalized() {
87 m_length_and_is_normalized &= ~static_cast<size_t>(1);
88 }
89
90 private:
91 constexpr WriteBuffer(char* buffer, size_t length)
92 : m_buffer(buffer), m_length_and_is_normalized(0) {
93 this->SetLength(length);
94 }
95
96 public:
97 static WriteBuffer Make(size_t length) {
98 if (void* alloc = Allocate(length); alloc != nullptr) {
99 return WriteBuffer(static_cast<char*>(alloc), length);
100 } else {
101 return WriteBuffer();
102 }
103 }
104
105 private:
106 constexpr void SetLength(size_t size) {
107 m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
108 }
109 };
110
111private:
112 const char* m_str;
113 WriteBuffer m_write_buffer;
114
115public:
116 constexpr Path() : m_str(EmptyPath), m_write_buffer() {
117 /* ... */
118 }
119
120 constexpr Path(const char* s) : m_str(s), m_write_buffer() {
121 m_write_buffer.SetNormalized();
122 }
123
124 constexpr ~Path() { /* ... */
125 }
126
127 constexpr Result SetShallowBuffer(const char* buffer) {
128 /* Check pre-conditions. */
129 ASSERT(m_write_buffer.GetLength() == 0);
130
131 /* Check the buffer is valid. */
132 R_UNLESS(buffer != nullptr, ResultNullptrArgument);
133
134 /* Set buffer. */
135 this->SetReadOnlyBuffer(buffer);
136
137 /* Note that we're normalized. */
138 this->SetNormalized();
139
140 R_SUCCEED();
141 }
142
143 constexpr const char* GetString() const {
144 /* Check pre-conditions. */
145 ASSERT(this->IsNormalized());
146
147 return m_str;
148 }
149
150 constexpr size_t GetLength() const {
151 if (std::is_constant_evaluated()) {
152 return Strlen(this->GetString());
153 } else {
154 return std::strlen(this->GetString());
155 }
156 }
157
158 constexpr bool IsEmpty() const {
159 return *m_str == '\x00';
160 }
161
162 constexpr bool IsMatchHead(const char* p, size_t len) const {
163 return Strncmp(this->GetString(), p, len) == 0;
164 }
165
166 Result Initialize(const Path& rhs) {
167 /* Check the other path is normalized. */
168 const bool normalized = rhs.IsNormalized();
169 R_UNLESS(normalized, ResultNotNormalized);
170
171 /* Allocate buffer for our path. */
172 const auto len = rhs.GetLength();
173 R_TRY(this->Preallocate(len + 1));
174
175 /* Copy the path. */
176 const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
177 R_UNLESS(copied == len, ResultUnexpectedInPathA);
178
179 /* Set normalized. */
180 this->SetNormalized();
181 R_SUCCEED();
182 }
183
184 Result Initialize(const char* path, size_t len) {
185 /* Check the path is valid. */
186 R_UNLESS(path != nullptr, ResultNullptrArgument);
187
188 /* Initialize. */
189 R_TRY(this->InitializeImpl(path, len));
190
191 /* Set not normalized. */
192 this->SetNotNormalized();
193
194 R_SUCCEED();
195 }
196
197 Result Initialize(const char* path) {
198 /* Check the path is valid. */
199 R_UNLESS(path != nullptr, ResultNullptrArgument);
200
201 R_RETURN(this->Initialize(path, std::strlen(path)));
202 }
203
204 Result InitializeWithReplaceBackslash(const char* path) {
205 /* Check the path is valid. */
206 R_UNLESS(path != nullptr, ResultNullptrArgument);
207
208 /* Initialize. */
209 R_TRY(this->InitializeImpl(path, std::strlen(path)));
210
211 /* Replace slashes as desired. */
212 if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
213 Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
214 }
215
216 /* Set not normalized. */
217 this->SetNotNormalized();
218
219 R_SUCCEED();
220 }
221
222 Result InitializeWithReplaceForwardSlashes(const char* path) {
223 /* Check the path is valid. */
224 R_UNLESS(path != nullptr, ResultNullptrArgument);
225
226 /* Initialize. */
227 R_TRY(this->InitializeImpl(path, std::strlen(path)));
228
229 /* Replace slashes as desired. */
230 if (m_write_buffer.GetLength() > 1) {
231 if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
232 p[0] = '\\';
233 p[1] = '\\';
234 }
235 }
236
237 /* Set not normalized. */
238 this->SetNotNormalized();
239
240 R_SUCCEED();
241 }
242
243 Result InitializeWithNormalization(const char* path, size_t size) {
244 /* Check the path is valid. */
245 R_UNLESS(path != nullptr, ResultNullptrArgument);
246
247 /* Initialize. */
248 R_TRY(this->InitializeImpl(path, size));
249
250 /* Set not normalized. */
251 this->SetNotNormalized();
252
253 /* Perform normalization. */
254 PathFlags path_flags;
255 if (IsPathRelative(m_str)) {
256 path_flags.AllowRelativePath();
257 } else if (IsWindowsPath(m_str, true)) {
258 path_flags.AllowWindowsPath();
259 } else {
260 /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then
261 * returns success. */
262 /* This seems like a bug. */
263 size_t dummy;
264 bool normalized;
265 R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy),
266 m_str));
267
268 this->SetNormalized();
269 R_SUCCEED();
270 }
271
272 /* Normalize. */
273 R_TRY(this->Normalize(path_flags));
274
275 this->SetNormalized();
276 R_SUCCEED();
277 }
278
279 Result InitializeWithNormalization(const char* path) {
280 /* Check the path is valid. */
281 R_UNLESS(path != nullptr, ResultNullptrArgument);
282
283 R_RETURN(this->InitializeWithNormalization(path, std::strlen(path)));
284 }
285
286 Result InitializeAsEmpty() {
287 /* Clear our buffer. */
288 this->ClearBuffer();
289
290 /* Set normalized. */
291 this->SetNormalized();
292
293 R_SUCCEED();
294 }
295
296 Result AppendChild(const char* child) {
297 /* Check the path is valid. */
298 R_UNLESS(child != nullptr, ResultNullptrArgument);
299
300 /* Basic checks. If we hvea a path and the child is empty, we have nothing to do. */
301 const char* c = child;
302 if (m_str[0]) {
303 /* Skip an early separator. */
304 if (*c == '/') {
305 ++c;
306 }
307
308 R_SUCCEED_IF(*c == '\x00');
309 }
310
311 /* If we don't have a string, we can just initialize. */
312 auto cur_len = std::strlen(m_str);
313 if (cur_len == 0) {
314 R_RETURN(this->Initialize(child));
315 }
316
317 /* Remove a trailing separator. */
318 if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') {
319 --cur_len;
320 }
321
322 /* Get the child path's length. */
323 auto child_len = std::strlen(c);
324
325 /* Reset our write buffer. */
326 WriteBuffer old_write_buffer;
327 if (m_write_buffer.Get() != nullptr) {
328 old_write_buffer = std::move(m_write_buffer);
329 this->ClearBuffer();
330 }
331
332 /* Pre-allocate the new buffer. */
333 R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
334
335 /* Get our write buffer. */
336 auto* dst = m_write_buffer.Get();
337 if (old_write_buffer.Get() != nullptr && cur_len > 0) {
338 Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
339 }
340
341 /* Add separator. */
342 dst[cur_len] = '/';
343
344 /* Copy the child path. */
345 const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1);
346 R_UNLESS(copied == child_len, ResultUnexpectedInPathA);
347
348 R_SUCCEED();
349 }
350
351 Result AppendChild(const Path& rhs) {
352 R_RETURN(this->AppendChild(rhs.GetString()));
353 }
354
355 Result Combine(const Path& parent, const Path& child) {
356 /* Get the lengths. */
357 const auto p_len = parent.GetLength();
358 const auto c_len = child.GetLength();
359
360 /* Allocate our buffer. */
361 R_TRY(this->Preallocate(p_len + c_len + 1));
362
363 /* Initialize as parent. */
364 R_TRY(this->Initialize(parent));
365
366 /* If we're empty, we can just initialize as child. */
367 if (this->IsEmpty()) {
368 R_TRY(this->Initialize(child));
369 } else {
370 /* Otherwise, we should append the child. */
371 R_TRY(this->AppendChild(child));
372 }
373
374 R_SUCCEED();
375 }
376
377 Result RemoveChild() {
378 /* If we don't have a write-buffer, ensure that we have one. */
379 if (m_write_buffer.Get() == nullptr) {
380 if (const auto len = std::strlen(m_str); len > 0) {
381 R_TRY(this->Preallocate(len));
382 Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
383 }
384 }
385
386 /* Check that it's possible for us to remove a child. */
387 auto* p = m_write_buffer.Get();
388 s32 len = std::strlen(p);
389 R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented);
390
391 /* Handle a trailing separator. */
392 if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) {
393 --len;
394 }
395
396 /* Remove the child path segment. */
397 while ((--len) >= 0 && p[len]) {
398 if (p[len] == '/' || p[len] == '\\') {
399 if (len > 0) {
400 p[len] = 0;
401 } else {
402 p[1] = 0;
403 len = 1;
404 }
405 break;
406 }
407 }
408
409 /* Check that length remains > 0. */
410 R_UNLESS(len > 0, ResultNotImplemented);
411
412 R_SUCCEED();
413 }
414
415 Result Normalize(const PathFlags& flags) {
416 /* If we're already normalized, nothing to do. */
417 R_SUCCEED_IF(this->IsNormalized());
418
419 /* Check if we're normalized. */
420 bool normalized;
421 size_t dummy;
422 R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str,
423 flags));
424
425 /* If we're not normalized, normalize. */
426 if (!normalized) {
427 /* Determine necessary buffer length. */
428 auto len = m_write_buffer.GetLength();
429 if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) {
430 len += 2;
431 }
432 if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) {
433 len += 1;
434 }
435
436 /* Allocate a new buffer. */
437 const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength);
438 auto buf = WriteBuffer::Make(size);
439 R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
440
441 /* Normalize into it. */
442 R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(),
443 m_write_buffer.GetLength(), flags));
444
445 /* Set the normalized buffer as our buffer. */
446 this->SetModifiableBuffer(std::move(buf));
447 }
448
449 /* Set normalized. */
450 this->SetNormalized();
451 R_SUCCEED();
452 }
453
454private:
455 void ClearBuffer() {
456 m_write_buffer.ResetBuffer();
457 m_str = EmptyPath;
458 }
459
460 void SetModifiableBuffer(WriteBuffer&& buffer) {
461 /* Check pre-conditions. */
462 ASSERT(buffer.Get() != nullptr);
463 ASSERT(buffer.GetLength() > 0);
464 ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
465
466 /* Get whether we're normalized. */
467 if (m_write_buffer.IsNormalized()) {
468 buffer.SetNormalized();
469 } else {
470 buffer.SetNotNormalized();
471 }
472
473 /* Set write buffer. */
474 m_write_buffer = std::move(buffer);
475 m_str = m_write_buffer.Get();
476 }
477
478 constexpr void SetReadOnlyBuffer(const char* buffer) {
479 m_str = buffer;
480 m_write_buffer.ResetBuffer();
481 }
482
483 Result Preallocate(size_t length) {
484 /* Allocate additional space, if needed. */
485 if (length > m_write_buffer.GetLength()) {
486 /* Allocate buffer. */
487 const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength);
488 auto buf = WriteBuffer::Make(size);
489 R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
490
491 /* Set write buffer. */
492 this->SetModifiableBuffer(std::move(buf));
493 }
494
495 R_SUCCEED();
496 }
497
498 Result InitializeImpl(const char* path, size_t size) {
499 if (size > 0 && path[0]) {
500 /* Pre allocate a buffer for the path. */
501 R_TRY(this->Preallocate(size + 1));
502
503 /* Copy the path. */
504 const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
505 R_UNLESS(copied >= size, ResultUnexpectedInPathA);
506 } else {
507 /* We can just clear the buffer. */
508 this->ClearBuffer();
509 }
510
511 R_SUCCEED();
512 }
513
514 constexpr char* GetWriteBuffer() {
515 ASSERT(m_write_buffer.Get() != nullptr);
516 return m_write_buffer.Get();
517 }
518
519 constexpr size_t GetWriteBufferLength() const {
520 return m_write_buffer.GetLength();
521 }
522
523 constexpr bool IsNormalized() const {
524 return m_write_buffer.IsNormalized();
525 }
526
527 constexpr void SetNormalized() {
528 m_write_buffer.SetNormalized();
529 }
530
531 constexpr void SetNotNormalized() {
532 m_write_buffer.SetNotNormalized();
533 }
534
535public:
536 bool operator==(const FileSys::Path& rhs) const {
537 return std::strcmp(this->GetString(), rhs.GetString()) == 0;
538 }
539 bool operator!=(const FileSys::Path& rhs) const {
540 return !(*this == rhs);
541 }
542 bool operator==(const char* p) const {
543 return std::strcmp(this->GetString(), p) == 0;
544 }
545 bool operator!=(const char* p) const {
546 return !(*this == p);
547 }
548};
549
550inline Result SetUpFixedPath(FileSys::Path* out, const char* s) {
551 /* Verify the path is normalized. */
552 bool normalized;
553 size_t dummy;
554 R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s));
555
556 R_UNLESS(normalized, ResultInvalidPathFormat);
557
558 /* Set the fixed path. */
559 R_RETURN(out->SetShallowBuffer(s));
560}
561
562constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) {
563 const char* const str = path.GetString();
564 return IsWindowsDrive(str) &&
565 (str[2] == StringTraits::DirectorySeparator ||
566 str[2] == StringTraits::AlternateDirectorySeparator) &&
567 str[3] == StringTraits::NullTerminator;
568}
569
570} // namespace FileSys
diff --git a/src/core/file_sys/fs_path_utility.h b/src/core/file_sys/fs_path_utility.h
new file mode 100644
index 000000000..276445707
--- /dev/null
+++ b/src/core/file_sys/fs_path_utility.h
@@ -0,0 +1,1240 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/assert.h"
7#include "common/common_types.h"
8#include "common/scope_exit.h"
9#include "core/file_sys/fs_directory.h"
10#include "core/file_sys/fs_memory_management.h"
11#include "core/file_sys/fs_string_util.h"
12#include "core/hle/result.h"
13
14namespace FileSys {
15
16constexpr inline size_t MountNameLengthMax = 15;
17using Result = ::Result;
18
19namespace StringTraits {
20
21constexpr inline char DirectorySeparator = '/';
22constexpr inline char DriveSeparator = ':';
23constexpr inline char Dot = '.';
24constexpr inline char NullTerminator = '\x00';
25
26constexpr inline char AlternateDirectorySeparator = '\\';
27
28constexpr inline const char InvalidCharacters[6] = {':', '*', '?', '<', '>', '|'};
29constexpr inline const char InvalidCharactersForHostName[6] = {':', '*', '<', '>', '|', '$'};
30constexpr inline const char InvalidCharactersForMountName[5] = {'*', '?', '<', '>', '|'};
31
32namespace impl {
33
34template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
35consteval u64 MakeInvalidCharacterMask(size_t n) {
36 u64 mask = 0;
37 for (size_t i = 0; i < NumInvalidCharacters; ++i) {
38 if ((static_cast<u64>(InvalidCharacterSet[i]) >> 6) == n) {
39 mask |= static_cast<u64>(1) << (static_cast<u64>(InvalidCharacterSet[i]) & 0x3F);
40 }
41 }
42 return mask;
43}
44
45template <const char* InvalidCharacterSet, size_t NumInvalidCharacters>
46constexpr bool IsInvalidCharacterImpl(char c) {
47 constexpr u64 Masks[4] = {
48 MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(0),
49 MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(1),
50 MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(2),
51 MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(3)};
52
53 return (Masks[static_cast<u64>(c) >> 6] &
54 (static_cast<u64>(1) << (static_cast<u64>(c) & 0x3F))) != 0;
55}
56
57} // namespace impl
58
59constexpr bool IsInvalidCharacter(char c) {
60 return impl::IsInvalidCharacterImpl<InvalidCharacters, size(InvalidCharacters)>(c);
61}
62constexpr bool IsInvalidCharacterForHostName(char c) {
63 return impl::IsInvalidCharacterImpl<InvalidCharactersForHostName,
64 size(InvalidCharactersForHostName)>(c);
65}
66constexpr bool IsInvalidCharacterForMountName(char c) {
67 return impl::IsInvalidCharacterImpl<InvalidCharactersForMountName,
68 size(InvalidCharactersForMountName)>(c);
69}
70
71} // namespace StringTraits
72
73constexpr inline size_t WindowsDriveLength = 2;
74constexpr inline size_t UncPathPrefixLength = 2;
75constexpr inline size_t DosDevicePathPrefixLength = 4;
76
77class PathFlags {
78private:
79 static constexpr u32 WindowsPathFlag = (1 << 0);
80 static constexpr u32 RelativePathFlag = (1 << 1);
81 static constexpr u32 EmptyPathFlag = (1 << 2);
82 static constexpr u32 MountNameFlag = (1 << 3);
83 static constexpr u32 BackslashFlag = (1 << 4);
84 static constexpr u32 AllCharactersFlag = (1 << 5);
85
86private:
87 u32 m_value;
88
89public:
90 constexpr PathFlags() : m_value(0) { /* ... */
91 }
92
93#define DECLARE_PATH_FLAG_HANDLER(__WHICH__) \
94 constexpr bool Is##__WHICH__##Allowed() const { return (m_value & __WHICH__##Flag) != 0; } \
95 constexpr void Allow##__WHICH__() { m_value |= __WHICH__##Flag; }
96
97 DECLARE_PATH_FLAG_HANDLER(WindowsPath)
98 DECLARE_PATH_FLAG_HANDLER(RelativePath)
99 DECLARE_PATH_FLAG_HANDLER(EmptyPath)
100 DECLARE_PATH_FLAG_HANDLER(MountName)
101 DECLARE_PATH_FLAG_HANDLER(Backslash)
102 DECLARE_PATH_FLAG_HANDLER(AllCharacters)
103
104#undef DECLARE_PATH_FLAG_HANDLER
105};
106
107template <typename T>
108 requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
109constexpr inline bool IsDosDevicePath(const T* path) {
110 ASSERT(path != nullptr);
111
112 using namespace StringTraits;
113
114 return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator &&
115 (path[2] == Dot || path[2] == '?') &&
116 (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator);
117}
118
119template <typename T>
120 requires(std::same_as<T, char> || std::same_as<T, wchar_t>)
121constexpr inline bool IsUncPath(const T* path, bool allow_forward_slash = true,
122 bool allow_back_slash = true) {
123 ASSERT(path != nullptr);
124
125 using namespace StringTraits;
126
127 return (allow_forward_slash && path[0] == DirectorySeparator &&
128 path[1] == DirectorySeparator) ||
129 (allow_back_slash && path[0] == AlternateDirectorySeparator &&
130 path[1] == AlternateDirectorySeparator);
131}
132
133constexpr inline bool IsWindowsDrive(const char* path) {
134 ASSERT(path != nullptr);
135
136 return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) &&
137 path[1] == StringTraits::DriveSeparator;
138}
139
140constexpr inline bool IsWindowsPath(const char* path, bool allow_forward_slash_unc) {
141 return IsWindowsDrive(path) || IsDosDevicePath(path) ||
142 IsUncPath(path, allow_forward_slash_unc, true);
143}
144
145constexpr inline int GetWindowsSkipLength(const char* path) {
146 if (IsDosDevicePath(path)) {
147 return DosDevicePathPrefixLength;
148 } else if (IsWindowsDrive(path)) {
149 return WindowsDriveLength;
150 } else if (IsUncPath(path)) {
151 return UncPathPrefixLength;
152 } else {
153 return 0;
154 }
155}
156
157constexpr inline bool IsPathAbsolute(const char* path) {
158 return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator;
159}
160
161constexpr inline bool IsPathRelative(const char* path) {
162 return path[0] && !IsPathAbsolute(path);
163}
164
165constexpr inline bool IsCurrentDirectory(const char* path) {
166 return path[0] == StringTraits::Dot &&
167 (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator);
168}
169
170constexpr inline bool IsParentDirectory(const char* path) {
171 return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot &&
172 (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator);
173}
174
175constexpr inline bool IsPathStartWithCurrentDirectory(const char* path) {
176 return IsCurrentDirectory(path) || IsParentDirectory(path);
177}
178
179constexpr inline bool IsSubPath(const char* lhs, const char* rhs) {
180 /* Check pre-conditions. */
181 ASSERT(lhs != nullptr);
182 ASSERT(rhs != nullptr);
183
184 /* Import StringTraits names for current scope. */
185 using namespace StringTraits;
186
187 /* Special case certain paths. */
188 if (IsUncPath(lhs) && !IsUncPath(rhs)) {
189 return false;
190 }
191 if (!IsUncPath(lhs) && IsUncPath(rhs)) {
192 return false;
193 }
194
195 if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator &&
196 rhs[1] != NullTerminator) {
197 return true;
198 }
199 if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator &&
200 lhs[1] != NullTerminator) {
201 return true;
202 }
203
204 /* Check subpath. */
205 for (size_t i = 0; /* ... */; ++i) {
206 if (lhs[i] == NullTerminator) {
207 return rhs[i] == DirectorySeparator;
208 } else if (rhs[i] == NullTerminator) {
209 return lhs[i] == DirectorySeparator;
210 } else if (lhs[i] != rhs[i]) {
211 return false;
212 }
213 }
214}
215
216/* Path utilities. */
217constexpr inline void Replace(char* dst, size_t dst_size, char old_char, char new_char) {
218 ASSERT(dst != nullptr);
219 for (char* cur = dst; cur < dst + dst_size && *cur; ++cur) {
220 if (*cur == old_char) {
221 *cur = new_char;
222 }
223 }
224}
225
226constexpr inline Result CheckUtf8(const char* s) {
227 /* Check pre-conditions. */
228 ASSERT(s != nullptr);
229
230 /* Iterate, checking for utf8-validity. */
231 while (*s) {
232 char utf8_buf[4] = {};
233
234 const auto pick_res = PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s));
235 R_UNLESS(pick_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
236
237 u32 dummy;
238 const auto cvt_res = ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf);
239 R_UNLESS(cvt_res == CharacterEncodingResult_Success, ResultInvalidPathFormat);
240 }
241
242 R_SUCCEED();
243}
244
245/* Path formatting. */
246class PathNormalizer {
247private:
248 enum class PathState {
249 Start,
250 Normal,
251 FirstSeparator,
252 Separator,
253 CurrentDir,
254 ParentDir,
255 };
256
257private:
258 static constexpr void ReplaceParentDirectoryPath(char* dst, const char* src) {
259 /* Use StringTraits names for remainder of scope. */
260 using namespace StringTraits;
261
262 /* Start with a dir-separator. */
263 dst[0] = DirectorySeparator;
264
265 auto i = 1;
266 while (src[i] != NullTerminator) {
267 if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) &&
268 src[i + 0] == Dot && src[i + 1] == Dot &&
269 (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) {
270 dst[i - 1] = DirectorySeparator;
271 dst[i + 0] = Dot;
272 dst[i + 1] = Dot;
273 dst[i + 2] = DirectorySeparator;
274 i += 3;
275 } else {
276 if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot &&
277 src[i + 1] == Dot && src[i + 2] == NullTerminator) {
278 dst[i - 1] = DirectorySeparator;
279 dst[i + 0] = Dot;
280 dst[i + 1] = Dot;
281 i += 2;
282 break;
283 }
284
285 dst[i] = src[i];
286 ++i;
287 }
288 }
289
290 dst[i] = StringTraits::NullTerminator;
291 }
292
293public:
294 static constexpr bool IsParentDirectoryPathReplacementNeeded(const char* path) {
295 /* Use StringTraits names for remainder of scope. */
296 using namespace StringTraits;
297
298 if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) {
299 return false;
300 }
301
302 /* Check to find a parent reference using alternate separators. */
303 if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) {
304 size_t i;
305 for (i = 0; path[i + 3] != NullTerminator; ++path) {
306 if (path[i + 1] != Dot || path[i + 2] != Dot) {
307 continue;
308 }
309
310 const char c0 = path[i + 0];
311 const char c3 = path[i + 3];
312
313 if (c0 == AlternateDirectorySeparator &&
314 (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator ||
315 c3 == NullTerminator)) {
316 return true;
317 }
318
319 if (c3 == AlternateDirectorySeparator &&
320 (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) {
321 return true;
322 }
323 }
324
325 if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot &&
326 path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) {
327 return true;
328 }
329 }
330
331 return false;
332 }
333
334 static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
335 bool allow_all_characters = false) {
336 /* Use StringTraits names for remainder of scope. */
337 using namespace StringTraits;
338
339 /* Parse the path. */
340 auto state = PathState::Start;
341 size_t len = 0;
342 while (path[len] != NullTerminator) {
343 /* Get the current character. */
344 const char c = path[len++];
345
346 /* Check the current character is valid. */
347 if (!allow_all_characters && state != PathState::Start) {
348 R_UNLESS(!IsInvalidCharacter(c), ResultInvalidCharacter);
349 }
350
351 /* Process depending on current state. */
352 switch (state) {
353 /* Import the PathState enums for convenience. */
354 using enum PathState;
355
356 case Start:
357 R_UNLESS(c == DirectorySeparator, ResultInvalidPathFormat);
358 state = FirstSeparator;
359 break;
360 case Normal:
361 if (c == DirectorySeparator) {
362 state = Separator;
363 }
364 break;
365 case FirstSeparator:
366 case Separator:
367 if (c == DirectorySeparator) {
368 *out = false;
369 R_SUCCEED();
370 }
371
372 if (c == Dot) {
373 state = CurrentDir;
374 } else {
375 state = Normal;
376 }
377 break;
378 case CurrentDir:
379 if (c == DirectorySeparator) {
380 *out = false;
381 R_SUCCEED();
382 }
383
384 if (c == Dot) {
385 state = ParentDir;
386 } else {
387 state = Normal;
388 }
389 break;
390 case ParentDir:
391 if (c == DirectorySeparator) {
392 *out = false;
393 R_SUCCEED();
394 }
395
396 state = Normal;
397 break;
398 default:
399 UNREACHABLE();
400 break;
401 }
402 }
403
404 /* Check the final state. */
405 switch (state) {
406 /* Import the PathState enums for convenience. */
407 using enum PathState;
408 case Start:
409 R_THROW(ResultInvalidPathFormat);
410 case Normal:
411 case FirstSeparator:
412 *out = true;
413 break;
414 case Separator:
415 case CurrentDir:
416 case ParentDir:
417 *out = false;
418 break;
419 default:
420 UNREACHABLE();
421 break;
422 }
423
424 /* Set the output length. */
425 *out_len = len;
426 R_SUCCEED();
427 }
428
429 static Result Normalize(char* dst, size_t* out_len, const char* path, size_t max_out_size,
430 bool is_windows_path, bool is_drive_relative_path,
431 bool allow_all_characters = false) {
432 /* Use StringTraits names for remainder of scope. */
433 using namespace StringTraits;
434
435 /* Prepare to iterate. */
436 const char* cur_path = path;
437 size_t total_len = 0;
438
439 /* If path begins with a separator, check that we're not drive relative. */
440 if (cur_path[0] != DirectorySeparator) {
441 R_UNLESS(is_drive_relative_path, ResultInvalidPathFormat);
442
443 dst[total_len++] = DirectorySeparator;
444 }
445
446 /* We're going to need to do path replacement, potentially. */
447 char* replacement_path = nullptr;
448 size_t replacement_path_size = 0;
449
450 SCOPE_EXIT({
451 if (replacement_path != nullptr) {
452 if (std::is_constant_evaluated()) {
453 delete[] replacement_path;
454 } else {
455 Deallocate(replacement_path, replacement_path_size);
456 }
457 }
458 });
459
460 /* Perform path replacement, if necessary. */
461 if (IsParentDirectoryPathReplacementNeeded(cur_path)) {
462 if (std::is_constant_evaluated()) {
463 replacement_path_size = EntryNameLengthMax + 1;
464 replacement_path = new char[replacement_path_size];
465 } else {
466 replacement_path_size = EntryNameLengthMax + 1;
467 replacement_path = static_cast<char*>(Allocate(replacement_path_size));
468 }
469
470 ReplaceParentDirectoryPath(replacement_path, cur_path);
471
472 cur_path = replacement_path;
473 }
474
475 /* Iterate, normalizing path components. */
476 bool skip_next_sep = false;
477 size_t i = 0;
478
479 while (cur_path[i] != NullTerminator) {
480 /* Process a directory separator, if we run into one. */
481 if (cur_path[i] == DirectorySeparator) {
482 /* Swallow separators. */
483 do {
484 ++i;
485 } while (cur_path[i] == DirectorySeparator);
486
487 /* Check if we hit end of string. */
488 if (cur_path[i] == NullTerminator) {
489 break;
490 }
491
492 /* If we aren't skipping the separator, write it, checking that we remain in bounds.
493 */
494 if (!skip_next_sep) {
495 if (total_len + 1 == max_out_size) {
496 dst[total_len] = NullTerminator;
497 *out_len = total_len;
498 R_THROW(ResultTooLongPath);
499 }
500
501 dst[total_len++] = DirectorySeparator;
502 }
503
504 /* Don't skip the next separator. */
505 skip_next_sep = false;
506 }
507
508 /* Get the length of the current directory component. */
509 size_t dir_len = 0;
510 while (cur_path[i + dir_len] != DirectorySeparator &&
511 cur_path[i + dir_len] != NullTerminator) {
512 /* Check for validity. */
513 if (!allow_all_characters) {
514 R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), ResultInvalidCharacter);
515 }
516
517 ++dir_len;
518 }
519
520 /* Handle the current dir component. */
521 if (IsCurrentDirectory(cur_path + i)) {
522 skip_next_sep = true;
523 } else if (IsParentDirectory(cur_path + i)) {
524 /* We should have just written a separator. */
525 ASSERT(dst[total_len - 1] == DirectorySeparator);
526
527 /* We should have started with a separator, for non-windows paths. */
528 if (!is_windows_path) {
529 ASSERT(dst[0] == DirectorySeparator);
530 }
531
532 /* Remove the previous component. */
533 if (total_len == 1) {
534 R_UNLESS(is_windows_path, ResultDirectoryUnobtainable);
535
536 --total_len;
537 } else {
538 total_len -= 2;
539
540 do {
541 if (dst[total_len] == DirectorySeparator) {
542 break;
543 }
544 } while ((--total_len) != 0);
545 }
546
547 /* We should be pointing to a directory separator, for non-windows paths. */
548 if (!is_windows_path) {
549 ASSERT(dst[total_len] == DirectorySeparator);
550 }
551
552 /* We should remain in bounds. */
553 ASSERT(total_len < max_out_size);
554 } else {
555 /* Copy, possibly truncating. */
556 if (total_len + dir_len + 1 > max_out_size) {
557 const size_t copy_len = max_out_size - (total_len + 1);
558
559 for (size_t j = 0; j < copy_len; ++j) {
560 dst[total_len++] = cur_path[i + j];
561 }
562
563 dst[total_len] = NullTerminator;
564 *out_len = total_len;
565 R_THROW(ResultTooLongPath);
566 }
567
568 for (size_t j = 0; j < dir_len; ++j) {
569 dst[total_len++] = cur_path[i + j];
570 }
571 }
572
573 /* Advance past the current directory component. */
574 i += dir_len;
575 }
576
577 if (skip_next_sep) {
578 --total_len;
579 }
580
581 if (total_len == 0 && max_out_size != 0) {
582 total_len = 1;
583 dst[0] = DirectorySeparator;
584 }
585
586 /* NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null
587 * terminator. */
588 R_UNLESS(max_out_size >= total_len - 1, ResultTooLongPath);
589
590 dst[total_len] = NullTerminator;
591
592 /* Check that the result path is normalized. */
593 bool is_normalized;
594 size_t dummy;
595 R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst,
596 allow_all_characters));
597
598 /* Assert that the result path is normalized. */
599 ASSERT(is_normalized);
600
601 /* Set the output length. */
602 *out_len = total_len;
603 R_SUCCEED();
604 }
605};
606
607class PathFormatter {
608private:
609 static constexpr Result CheckSharedName(const char* name, size_t len) {
610 /* Use StringTraits names for remainder of scope. */
611 using namespace StringTraits;
612
613 if (len == 1) {
614 R_UNLESS(name[0] != Dot, ResultInvalidPathFormat);
615 } else if (len == 2) {
616 R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
617 }
618
619 for (size_t i = 0; i < len; ++i) {
620 R_UNLESS(!IsInvalidCharacter(name[i]), ResultInvalidCharacter);
621 }
622
623 R_SUCCEED();
624 }
625
626 static constexpr Result CheckHostName(const char* name, size_t len) {
627 /* Use StringTraits names for remainder of scope. */
628 using namespace StringTraits;
629
630 if (len == 2) {
631 R_UNLESS(name[0] != Dot || name[1] != Dot, ResultInvalidPathFormat);
632 }
633
634 for (size_t i = 0; i < len; ++i) {
635 R_UNLESS(!IsInvalidCharacterForHostName(name[i]), ResultInvalidCharacter);
636 }
637
638 R_SUCCEED();
639 }
640
641 static constexpr Result CheckInvalidBackslash(bool* out_contains_backslash, const char* path,
642 bool allow_backslash) {
643 /* Use StringTraits names for remainder of scope. */
644 using namespace StringTraits;
645
646 /* Default to no backslashes, so we can just write if we see one. */
647 *out_contains_backslash = false;
648
649 while (*path != NullTerminator) {
650 if (*(path++) == AlternateDirectorySeparator) {
651 *out_contains_backslash = true;
652
653 R_UNLESS(allow_backslash, ResultInvalidCharacter);
654 }
655 }
656
657 R_SUCCEED();
658 }
659
660public:
661 static constexpr Result CheckPathFormat(const char* path, const PathFlags& flags) {
662 bool normalized;
663 size_t len;
664 R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags));
665 }
666
667 static constexpr Result SkipMountName(const char** out, size_t* out_len, const char* path) {
668 R_RETURN(ParseMountName(out, out_len, nullptr, 0, path));
669 }
670
671 static constexpr Result ParseMountName(const char** out, size_t* out_len, char* out_mount_name,
672 size_t out_mount_name_buffer_size, const char* path) {
673 /* Check pre-conditions. */
674 ASSERT(path != nullptr);
675 ASSERT(out_len != nullptr);
676 ASSERT(out != nullptr);
677 ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0));
678
679 /* Use StringTraits names for remainder of scope. */
680 using namespace StringTraits;
681
682 /* Determine max mount length. */
683 const auto max_mount_len =
684 out_mount_name_buffer_size == 0
685 ? MountNameLengthMax + 1
686 : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size);
687
688 /* Parse the path until we see a drive separator. */
689 size_t mount_len = 0;
690 for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) {
691 const char c = path[mount_len];
692
693 /* If we see a drive separator, advance, then we're done with the pre-drive separator
694 * part of the mount. */
695 if (c == DriveSeparator) {
696 ++mount_len;
697 break;
698 }
699
700 /* If we see a directory separator, we're not in a mount name. */
701 if (c == DirectorySeparator || c == AlternateDirectorySeparator) {
702 *out = path;
703 *out_len = 0;
704 R_SUCCEED();
705 }
706 }
707
708 /* Check to be sure we're actually looking at a mount name. */
709 if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) {
710 *out = path;
711 *out_len = 0;
712 R_SUCCEED();
713 }
714
715 /* Check that all characters in the mount name are allowable. */
716 for (size_t i = 0; i < mount_len; ++i) {
717 R_UNLESS(!IsInvalidCharacterForMountName(path[i]), ResultInvalidCharacter);
718 }
719
720 /* Copy out the mount name. */
721 if (out_mount_name_buffer_size > 0) {
722 R_UNLESS(mount_len < out_mount_name_buffer_size, ResultTooLongPath);
723
724 for (size_t i = 0; i < mount_len; ++i) {
725 out_mount_name[i] = path[i];
726 }
727 out_mount_name[mount_len] = NullTerminator;
728 }
729
730 /* Set the output. */
731 *out = path + mount_len;
732 *out_len = mount_len;
733 R_SUCCEED();
734 }
735
736 static constexpr Result SkipRelativeDotPath(const char** out, size_t* out_len,
737 const char* path) {
738 R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path));
739 }
740
741 static constexpr Result ParseRelativeDotPath(const char** out, size_t* out_len,
742 char* out_relative,
743 size_t out_relative_buffer_size,
744 const char* path) {
745 /* Check pre-conditions. */
746 ASSERT(path != nullptr);
747 ASSERT(out_len != nullptr);
748 ASSERT(out != nullptr);
749 ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0));
750
751 /* Use StringTraits names for remainder of scope. */
752 using namespace StringTraits;
753
754 /* Initialize the output buffer, if we have one. */
755 if (out_relative_buffer_size > 0) {
756 out_relative[0] = NullTerminator;
757 }
758
759 /* Check if the path is relative. */
760 if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator ||
761 path[1] == AlternateDirectorySeparator)) {
762 if (out_relative_buffer_size > 0) {
763 R_UNLESS(out_relative_buffer_size >= 2, ResultTooLongPath);
764
765 out_relative[0] = Dot;
766 out_relative[1] = NullTerminator;
767 }
768
769 *out = path + 1;
770 *out_len = 1;
771 R_SUCCEED();
772 }
773
774 /* Ensure the path isn't a parent directory. */
775 R_UNLESS(!(path[0] == Dot && path[1] == Dot), ResultDirectoryUnobtainable);
776
777 /* There was no relative dot path. */
778 *out = path;
779 *out_len = 0;
780 R_SUCCEED();
781 }
782
783 static constexpr Result SkipWindowsPath(const char** out, size_t* out_len, bool* out_normalized,
784 const char* path, bool has_mount_name) {
785 /* We're normalized if and only if the parsing doesn't throw ResultNotNormalized(). */
786 *out_normalized = true;
787
788 R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) {
789 R_CATCH(ResultNotNormalized) {
790 *out_normalized = false;
791 }
792 }
793 R_END_TRY_CATCH;
794 ON_RESULT_INCLUDED(ResultNotNormalized) {
795 *out_normalized = false;
796 };
797
798 R_SUCCEED();
799 }
800
801 static constexpr Result ParseWindowsPath(const char** out, size_t* out_len, char* out_win,
802 size_t out_win_buffer_size, const char* path,
803 bool has_mount_name) {
804 /* Check pre-conditions. */
805 ASSERT(path != nullptr);
806 ASSERT(out_len != nullptr);
807 ASSERT(out != nullptr);
808 ASSERT((out_win == nullptr) == (out_win_buffer_size == 0));
809
810 /* Use StringTraits names for remainder of scope. */
811 using namespace StringTraits;
812
813 /* Initialize the output buffer, if we have one. */
814 if (out_win_buffer_size > 0) {
815 out_win[0] = NullTerminator;
816 }
817
818 /* Handle path start. */
819 const char* cur_path = path;
820 if (has_mount_name && path[0] == DirectorySeparator) {
821 if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) {
822 R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
823
824 ++cur_path;
825 } else if (IsWindowsDrive(path + 1)) {
826 R_UNLESS(out_win_buffer_size > 0, ResultNotNormalized);
827
828 ++cur_path;
829 }
830 }
831
832 /* Handle windows drive. */
833 if (IsWindowsDrive(cur_path)) {
834 /* Parse up to separator. */
835 size_t win_path_len = WindowsDriveLength;
836 for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) {
837 R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), ResultInvalidCharacter);
838
839 if (cur_path[win_path_len] == DirectorySeparator ||
840 cur_path[win_path_len] == AlternateDirectorySeparator) {
841 break;
842 }
843 }
844
845 /* Ensure that we're normalized, if we're required to be. */
846 if (out_win_buffer_size == 0) {
847 for (size_t i = 0; i < win_path_len; ++i) {
848 R_UNLESS(cur_path[i] != AlternateDirectorySeparator, ResultNotNormalized);
849 }
850 } else {
851 /* Ensure we can copy into the normalized buffer. */
852 R_UNLESS(win_path_len < out_win_buffer_size, ResultTooLongPath);
853
854 for (size_t i = 0; i < win_path_len; ++i) {
855 out_win[i] = cur_path[i];
856 }
857 out_win[win_path_len] = NullTerminator;
858
859 Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator);
860 }
861
862 *out = cur_path + win_path_len;
863 *out_len = win_path_len;
864 R_SUCCEED();
865 }
866
867 /* Handle DOS device. */
868 if (IsDosDevicePath(cur_path)) {
869 size_t dos_prefix_len = DosDevicePathPrefixLength;
870
871 if (IsWindowsDrive(cur_path + dos_prefix_len)) {
872 dos_prefix_len += WindowsDriveLength;
873 } else {
874 --dos_prefix_len;
875 }
876
877 if (out_win_buffer_size > 0) {
878 /* Ensure we can copy into the normalized buffer. */
879 R_UNLESS(dos_prefix_len < out_win_buffer_size, ResultTooLongPath);
880
881 for (size_t i = 0; i < dos_prefix_len; ++i) {
882 out_win[i] = cur_path[i];
883 }
884 out_win[dos_prefix_len] = NullTerminator;
885
886 Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
887 }
888
889 *out = cur_path + dos_prefix_len;
890 *out_len = dos_prefix_len;
891 R_SUCCEED();
892 }
893
894 /* Handle UNC path. */
895 if (IsUncPath(cur_path, false, true)) {
896 const char* final_path = cur_path;
897
898 R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, ResultInvalidPathFormat);
899 R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator,
900 ResultInvalidPathFormat);
901
902 size_t cur_component_offset = 0;
903 size_t pos = UncPathPrefixLength;
904 for (/* ... */; cur_path[pos] != NullTerminator; ++pos) {
905 if (cur_path[pos] == DirectorySeparator ||
906 cur_path[pos] == AlternateDirectorySeparator) {
907 if (cur_component_offset != 0) {
908 R_TRY(CheckSharedName(cur_path + cur_component_offset,
909 pos - cur_component_offset));
910
911 final_path = cur_path + pos;
912 break;
913 }
914
915 R_UNLESS(cur_path[pos + 1] != DirectorySeparator, ResultInvalidPathFormat);
916 R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator,
917 ResultInvalidPathFormat);
918
919 R_TRY(CheckHostName(cur_path + 2, pos - 2));
920
921 cur_component_offset = pos + 1;
922 }
923 }
924
925 R_UNLESS(cur_component_offset != pos, ResultInvalidPathFormat);
926
927 if (cur_component_offset != 0 && final_path == cur_path) {
928 R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset));
929
930 final_path = cur_path + pos;
931 }
932
933 size_t unc_prefix_len = final_path - cur_path;
934
935 /* Ensure that we're normalized, if we're required to be. */
936 if (out_win_buffer_size == 0) {
937 for (size_t i = 0; i < unc_prefix_len; ++i) {
938 R_UNLESS(cur_path[i] != DirectorySeparator, ResultNotNormalized);
939 }
940 } else {
941 /* Ensure we can copy into the normalized buffer. */
942 R_UNLESS(unc_prefix_len < out_win_buffer_size, ResultTooLongPath);
943
944 for (size_t i = 0; i < unc_prefix_len; ++i) {
945 out_win[i] = cur_path[i];
946 }
947 out_win[unc_prefix_len] = NullTerminator;
948
949 Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator);
950 }
951
952 *out = cur_path + unc_prefix_len;
953 *out_len = unc_prefix_len;
954 R_SUCCEED();
955 }
956
957 /* There's no windows path to parse. */
958 *out = path;
959 *out_len = 0;
960 R_SUCCEED();
961 }
962
963 static constexpr Result IsNormalized(bool* out, size_t* out_len, const char* path,
964 const PathFlags& flags = {}) {
965 /* Ensure nothing is null. */
966 R_UNLESS(out != nullptr, ResultNullptrArgument);
967 R_UNLESS(out_len != nullptr, ResultNullptrArgument);
968 R_UNLESS(path != nullptr, ResultNullptrArgument);
969
970 /* Verify that the path is valid utf-8. */
971 R_TRY(CheckUtf8(path));
972
973 /* Use StringTraits names for remainder of scope. */
974 using namespace StringTraits;
975
976 /* Handle the case where the path is empty. */
977 if (path[0] == NullTerminator) {
978 R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
979
980 *out = true;
981 *out_len = 0;
982 R_SUCCEED();
983 }
984
985 /* All normalized paths start with a directory separator...unless they're windows paths,
986 * relative paths, or have mount names. */
987 if (path[0] != DirectorySeparator) {
988 R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() ||
989 flags.IsMountNameAllowed(),
990 ResultInvalidPathFormat);
991 }
992
993 /* Check that the path is allowed to be a windows path, if it is. */
994 if (IsWindowsPath(path, false)) {
995 R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
996 }
997
998 /* Skip past the mount name, if one is present. */
999 size_t total_len = 0;
1000 size_t mount_name_len = 0;
1001 R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path));
1002
1003 /* If we had a mount name, check that that was allowed. */
1004 if (mount_name_len > 0) {
1005 R_UNLESS(flags.IsMountNameAllowed(), ResultInvalidPathFormat);
1006
1007 total_len += mount_name_len;
1008 }
1009
1010 /* Check that the path starts as a normalized path should. */
1011 if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) &&
1012 !IsWindowsPath(path, false)) {
1013 R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
1014 R_UNLESS(!IsInvalidCharacter(path[0]), ResultInvalidPathFormat);
1015
1016 *out = false;
1017 R_SUCCEED();
1018 }
1019
1020 /* Process relative path. */
1021 size_t relative_len = 0;
1022 R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path));
1023
1024 /* If we have a relative path, check that was allowed. */
1025 if (relative_len > 0) {
1026 R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
1027
1028 total_len += relative_len;
1029
1030 if (path[0] == NullTerminator) {
1031 *out = true;
1032 *out_len = total_len;
1033 R_SUCCEED();
1034 }
1035 }
1036
1037 /* Process windows path. */
1038 size_t windows_len = 0;
1039 bool normalized_win = false;
1040 R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len),
1041 std::addressof(normalized_win), path, mount_name_len > 0));
1042
1043 /* If the windows path wasn't normalized, we're not normalized. */
1044 if (!normalized_win) {
1045 R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
1046
1047 *out = false;
1048 R_SUCCEED();
1049 }
1050
1051 /* If we had a windows path, check that was allowed. */
1052 if (windows_len > 0) {
1053 R_UNLESS(flags.IsWindowsPathAllowed(), ResultInvalidPathFormat);
1054
1055 total_len += windows_len;
1056
1057 /* We can't have both a relative path and a windows path. */
1058 R_UNLESS(relative_len == 0, ResultInvalidPathFormat);
1059
1060 /* A path ending in a windows path isn't normalized. */
1061 if (path[0] == NullTerminator) {
1062 *out = false;
1063 R_SUCCEED();
1064 }
1065
1066 /* Check that there are no windows directory separators in the path. */
1067 for (size_t i = 0; path[i] != NullTerminator; ++i) {
1068 if (path[i] == AlternateDirectorySeparator) {
1069 *out = false;
1070 R_SUCCEED();
1071 }
1072 }
1073 }
1074
1075 /* Check that parent directory replacement is not needed if backslashes are allowed. */
1076 if (flags.IsBackslashAllowed() &&
1077 PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) {
1078 *out = false;
1079 R_SUCCEED();
1080 }
1081
1082 /* Check that the backslash state is valid. */
1083 bool is_backslash_contained = false;
1084 R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path,
1085 flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
1086
1087 /* Check that backslashes are contained only if allowed. */
1088 if (is_backslash_contained && !flags.IsBackslashAllowed()) {
1089 *out = false;
1090 R_SUCCEED();
1091 }
1092
1093 /* Check that the final result path is normalized. */
1094 size_t normal_len = 0;
1095 R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path,
1096 flags.IsAllCharactersAllowed()));
1097
1098 /* Add the normal length. */
1099 total_len += normal_len;
1100
1101 /* Set the output length. */
1102 *out_len = total_len;
1103 R_SUCCEED();
1104 }
1105
1106 static Result Normalize(char* dst, size_t dst_size, const char* path, size_t path_len,
1107 const PathFlags& flags) {
1108 /* Use StringTraits names for remainder of scope. */
1109 using namespace StringTraits;
1110
1111 /* Prepare to iterate. */
1112 const char* src = path;
1113 size_t cur_pos = 0;
1114 bool is_windows_path = false;
1115
1116 /* Check if the path is empty. */
1117 if (src[0] == NullTerminator) {
1118 if (dst_size != 0) {
1119 dst[0] = NullTerminator;
1120 }
1121
1122 R_UNLESS(flags.IsEmptyPathAllowed(), ResultInvalidPathFormat);
1123
1124 R_SUCCEED();
1125 }
1126
1127 /* Handle a mount name. */
1128 size_t mount_name_len = 0;
1129 if (flags.IsMountNameAllowed()) {
1130 R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos,
1131 dst_size - cur_pos, src));
1132
1133 cur_pos += mount_name_len;
1134 }
1135
1136 /* Handle a drive-relative prefix. */
1137 bool is_drive_relative = false;
1138 if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) &&
1139 !IsWindowsPath(src, false)) {
1140 R_UNLESS(flags.IsRelativePathAllowed(), ResultInvalidPathFormat);
1141 R_UNLESS(!IsInvalidCharacter(src[0]), ResultInvalidPathFormat);
1142
1143 dst[cur_pos++] = Dot;
1144 is_drive_relative = true;
1145 }
1146
1147 size_t relative_len = 0;
1148 if (flags.IsRelativePathAllowed()) {
1149 R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
1150
1151 R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len),
1152 dst + cur_pos, dst_size - cur_pos, src));
1153
1154 cur_pos += relative_len;
1155
1156 if (src[0] == NullTerminator) {
1157 R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
1158
1159 dst[cur_pos] = NullTerminator;
1160 R_SUCCEED();
1161 }
1162 }
1163
1164 /* Handle a windows path. */
1165 if (flags.IsWindowsPathAllowed()) {
1166 const char* const orig = src;
1167
1168 R_UNLESS(cur_pos < dst_size, ResultTooLongPath);
1169
1170 size_t windows_len = 0;
1171 R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos,
1172 dst_size - cur_pos, src, mount_name_len != 0));
1173
1174 cur_pos += windows_len;
1175
1176 if (src[0] == NullTerminator) {
1177 /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */
1178 R_UNLESS(cur_pos + 1 < dst_size, ResultTooLongPath);
1179
1180 dst[cur_pos + 0] = DirectorySeparator;
1181 dst[cur_pos + 1] = NullTerminator;
1182 R_SUCCEED();
1183 }
1184
1185 if ((src - orig) > 0) {
1186 is_windows_path = true;
1187 }
1188 }
1189
1190 /* Check for invalid backslash. */
1191 bool backslash_contained = false;
1192 R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src,
1193 flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed()));
1194
1195 /* Handle backslash replacement as necessary. */
1196 if (backslash_contained && flags.IsWindowsPathAllowed()) {
1197 /* Create a temporary buffer holding a slash-replaced version of the path. */
1198 /* NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path,
1199 * despite having skipped some of it already. */
1200 const size_t replaced_src_len = path_len - (src - path);
1201
1202 char* replaced_src = nullptr;
1203 SCOPE_EXIT({
1204 if (replaced_src != nullptr) {
1205 if (std::is_constant_evaluated()) {
1206 delete[] replaced_src;
1207 } else {
1208 Deallocate(replaced_src, replaced_src_len);
1209 }
1210 }
1211 });
1212
1213 if (std::is_constant_evaluated()) {
1214 replaced_src = new char[replaced_src_len];
1215 } else {
1216 replaced_src = static_cast<char*>(Allocate(replaced_src_len));
1217 }
1218
1219 Strlcpy<char>(replaced_src, src, replaced_src_len);
1220
1221 Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator,
1222 DirectorySeparator);
1223
1224 size_t dummy;
1225 R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src,
1226 dst_size - cur_pos, is_windows_path, is_drive_relative,
1227 flags.IsAllCharactersAllowed()));
1228 } else {
1229 /* We can just do normalization. */
1230 size_t dummy;
1231 R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src,
1232 dst_size - cur_pos, is_windows_path, is_drive_relative,
1233 flags.IsAllCharactersAllowed()));
1234 }
1235
1236 R_SUCCEED();
1237 }
1238};
1239
1240} // namespace FileSys
diff --git a/src/core/file_sys/fs_string_util.h b/src/core/file_sys/fs_string_util.h
new file mode 100644
index 000000000..68114e72c
--- /dev/null
+++ b/src/core/file_sys/fs_string_util.h
@@ -0,0 +1,241 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/assert.h"
7
8namespace FileSys {
9
10template <typename T>
11constexpr int Strlen(const T* str) {
12 ASSERT(str != nullptr);
13
14 int length = 0;
15 while (*str++) {
16 ++length;
17 }
18
19 return length;
20}
21
22template <typename T>
23constexpr int Strnlen(const T* str, int count) {
24 ASSERT(str != nullptr);
25 ASSERT(count >= 0);
26
27 int length = 0;
28 while (count-- && *str++) {
29 ++length;
30 }
31
32 return length;
33}
34
35template <typename T>
36constexpr int Strncmp(const T* lhs, const T* rhs, int count) {
37 ASSERT(lhs != nullptr);
38 ASSERT(rhs != nullptr);
39 ASSERT(count >= 0);
40
41 if (count == 0) {
42 return 0;
43 }
44
45 T l, r;
46 do {
47 l = *(lhs++);
48 r = *(rhs++);
49 } while (l && (l == r) && (--count));
50
51 return l - r;
52}
53
54template <typename T>
55static constexpr int Strlcpy(T* dst, const T* src, int count) {
56 ASSERT(dst != nullptr);
57 ASSERT(src != nullptr);
58
59 const T* cur = src;
60 if (count > 0) {
61 while ((--count) && *cur) {
62 *(dst++) = *(cur++);
63 }
64 *dst = 0;
65 }
66
67 while (*cur) {
68 cur++;
69 }
70
71 return static_cast<int>(cur - src);
72}
73
74/* std::size() does not support zero-size C arrays. We're fixing that. */
75template <class C>
76constexpr auto size(const C& c) -> decltype(c.size()) {
77 return std::size(c);
78}
79
80template <class C>
81constexpr std::size_t size(const C& c) {
82 if constexpr (sizeof(C) == 0) {
83 return 0;
84 } else {
85 return std::size(c);
86 }
87}
88
89enum CharacterEncodingResult {
90 CharacterEncodingResult_Success = 0,
91 CharacterEncodingResult_InsufficientLength = 1,
92 CharacterEncodingResult_InvalidFormat = 2,
93};
94
95namespace impl {
96
97class CharacterEncodingHelper {
98public:
99 static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = {
100 -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,
101 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,
102 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,
103 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,
104 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,
105 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,
106 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,
107 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,
108 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,
109 };
110
111 static constexpr char GetUtf8NBytes(size_t i) {
112 return static_cast<char>(Utf8NBytesInnerTable[1 + i]);
113 }
114};
115
116} // namespace impl
117
118constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32* dst, const char* src) {
119 /* Check pre-conditions. */
120 ASSERT(dst != nullptr);
121 ASSERT(src != nullptr);
122
123 /* Perform the conversion. */
124 const auto* p = src;
125 switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[0]))) {
126 case 1:
127 *dst = static_cast<u32>(p[0]);
128 return CharacterEncodingResult_Success;
129 case 2:
130 if ((static_cast<u32>(p[0]) & 0x1E) != 0) {
131 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
132 0) {
133 *dst = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
134 return CharacterEncodingResult_Success;
135 }
136 }
137 break;
138 case 3:
139 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
140 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
141 const u32 c = (static_cast<u32>(p[0] & 0xF) << 12) |
142 (static_cast<u32>(p[1] & 0x3F) << 6) |
143 (static_cast<u32>(p[2] & 0x3F) << 0);
144 if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
145 *dst = c;
146 return CharacterEncodingResult_Success;
147 }
148 }
149 return CharacterEncodingResult_InvalidFormat;
150 case 4:
151 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
152 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
153 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
154 const u32 c =
155 (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
156 (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
157 if (c >= 0x10000 && c < 0x110000) {
158 *dst = c;
159 return CharacterEncodingResult_Success;
160 }
161 }
162 return CharacterEncodingResult_InvalidFormat;
163 default:
164 break;
165 }
166
167 /* We failed to convert. */
168 return CharacterEncodingResult_InvalidFormat;
169}
170
171constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char* dst,
172 const char** str) {
173 /* Check pre-conditions. */
174 ASSERT(dst != nullptr);
175 ASSERT(str != nullptr);
176 ASSERT(*str != nullptr);
177
178 /* Clear the output. */
179 dst[0] = 0;
180 dst[1] = 0;
181 dst[2] = 0;
182 dst[3] = 0;
183
184 /* Perform the conversion. */
185 const auto* p = *str;
186 u32 c = static_cast<u32>(*p);
187 switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) {
188 case 1:
189 dst[0] = (*str)[0];
190 ++(*str);
191 break;
192 case 2:
193 if ((p[0] & 0x1E) != 0) {
194 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) ==
195 0) {
196 c = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0);
197 dst[0] = (*str)[0];
198 dst[1] = (*str)[1];
199 (*str) += 2;
200 break;
201 }
202 }
203 return CharacterEncodingResult_InvalidFormat;
204 case 3:
205 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
206 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) {
207 c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) |
208 (static_cast<u32>(p[2] & 0x3F) << 0);
209 if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) {
210 dst[0] = (*str)[0];
211 dst[1] = (*str)[1];
212 dst[2] = (*str)[2];
213 (*str) += 3;
214 break;
215 }
216 }
217 return CharacterEncodingResult_InvalidFormat;
218 case 4:
219 if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 &&
220 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 &&
221 impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) {
222 c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) |
223 (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0);
224 if (c >= 0x10000 && c < 0x110000) {
225 dst[0] = (*str)[0];
226 dst[1] = (*str)[1];
227 dst[2] = (*str)[2];
228 dst[3] = (*str)[3];
229 (*str) += 4;
230 break;
231 }
232 }
233 return CharacterEncodingResult_InvalidFormat;
234 default:
235 return CharacterEncodingResult_InvalidFormat;
236 }
237
238 return CharacterEncodingResult_Success;
239}
240
241} // namespace FileSys
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
index 1e8ef366e..39690018b 100644
--- a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
+++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "core/file_sys/fs_filesystem.h"
4#include "core/file_sys/savedata_factory.h" 5#include "core/file_sys/savedata_factory.h"
5#include "core/hle/service/filesystem/fsp/fs_i_directory.h" 6#include "core/hle/service/filesystem/fsp/fs_i_directory.h"
6#include "core/hle/service/ipc_helpers.h" 7#include "core/hle/service/ipc_helpers.h"
diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.h b/src/core/hle/service/filesystem/fsp/fs_i_directory.h
index 9f5d7c054..793ecfcd7 100644
--- a/src/core/hle/service/filesystem/fsp/fs_i_directory.h
+++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.h
@@ -3,12 +3,14 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/file_sys/fs_filesystem.h"
7#include "core/file_sys/vfs/vfs.h" 6#include "core/file_sys/vfs/vfs.h"
8#include "core/hle/service/filesystem/filesystem.h" 7#include "core/hle/service/filesystem/filesystem.h"
9#include "core/hle/service/filesystem/fsp/fsp_util.h"
10#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
11 9
10namespace FileSys {
11struct DirectoryEntry;
12}
13
12namespace Service::FileSystem { 14namespace Service::FileSystem {
13 15
14class IDirectory final : public ServiceFramework<IDirectory> { 16class IDirectory final : public ServiceFramework<IDirectory> {