summaryrefslogtreecommitdiff
path: root/src/common/fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/fs')
-rw-r--r--src/common/fs/file.cpp392
-rw-r--r--src/common/fs/file.h453
-rw-r--r--src/common/fs/fs.cpp610
-rw-r--r--src/common/fs/fs.h582
-rw-r--r--src/common/fs/fs_paths.h27
-rw-r--r--src/common/fs/fs_types.h73
-rw-r--r--src/common/fs/fs_util.cpp13
-rw-r--r--src/common/fs/fs_util.h25
-rw-r--r--src/common/fs/path_util.cpp432
-rw-r--r--src/common/fs/path_util.h309
10 files changed, 2916 insertions, 0 deletions
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
new file mode 100644
index 000000000..9f3de1cb0
--- /dev/null
+++ b/src/common/fs/file.cpp
@@ -0,0 +1,392 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9
10#ifdef _WIN32
11#include <io.h>
12#include <share.h>
13#else
14#include <unistd.h>
15#endif
16
17#ifdef _MSC_VER
18#define fileno _fileno
19#define fseeko _fseeki64
20#define ftello _ftelli64
21#endif
22
23namespace Common::FS {
24
25namespace fs = std::filesystem;
26
27namespace {
28
29#ifdef _WIN32
30
31/**
32 * Converts the file access mode and file type enums to a file access mode wide string.
33 *
34 * @param mode File access mode
35 * @param type File type
36 *
37 * @returns A pointer to a wide string representing the file access mode.
38 */
39[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
40 switch (type) {
41 case FileType::BinaryFile:
42 switch (mode) {
43 case FileAccessMode::Read:
44 return L"rb";
45 case FileAccessMode::Write:
46 return L"wb";
47 case FileAccessMode::Append:
48 return L"ab";
49 case FileAccessMode::ReadWrite:
50 return L"r+b";
51 case FileAccessMode::ReadAppend:
52 return L"a+b";
53 }
54 break;
55 case FileType::TextFile:
56 switch (mode) {
57 case FileAccessMode::Read:
58 return L"r";
59 case FileAccessMode::Write:
60 return L"w";
61 case FileAccessMode::Append:
62 return L"a";
63 case FileAccessMode::ReadWrite:
64 return L"r+";
65 case FileAccessMode::ReadAppend:
66 return L"a+";
67 }
68 break;
69 }
70
71 return L"";
72}
73
74/**
75 * Converts the file-share access flag enum to a Windows defined file-share access flag.
76 *
77 * @param flag File-share access flag
78 *
79 * @returns Windows defined file-share access flag.
80 */
81[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
82 switch (flag) {
83 case FileShareFlag::ShareNone:
84 default:
85 return _SH_DENYRW;
86 case FileShareFlag::ShareReadOnly:
87 return _SH_DENYWR;
88 case FileShareFlag::ShareWriteOnly:
89 return _SH_DENYRD;
90 case FileShareFlag::ShareReadWrite:
91 return _SH_DENYNO;
92 }
93}
94
95#else
96
97/**
98 * Converts the file access mode and file type enums to a file access mode string.
99 *
100 * @param mode File access mode
101 * @param type File type
102 *
103 * @returns A pointer to a string representing the file access mode.
104 */
105[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
106 switch (type) {
107 case FileType::BinaryFile:
108 switch (mode) {
109 case FileAccessMode::Read:
110 return "rb";
111 case FileAccessMode::Write:
112 return "wb";
113 case FileAccessMode::Append:
114 return "ab";
115 case FileAccessMode::ReadWrite:
116 return "r+b";
117 case FileAccessMode::ReadAppend:
118 return "a+b";
119 }
120 break;
121 case FileType::TextFile:
122 switch (mode) {
123 case FileAccessMode::Read:
124 return "r";
125 case FileAccessMode::Write:
126 return "w";
127 case FileAccessMode::Append:
128 return "a";
129 case FileAccessMode::ReadWrite:
130 return "r+";
131 case FileAccessMode::ReadAppend:
132 return "a+";
133 }
134 break;
135 }
136
137 return "";
138}
139
140#endif
141
142/**
143 * Converts the seek origin enum to a seek origin integer.
144 *
145 * @param origin Seek origin
146 *
147 * @returns Seek origin integer.
148 */
149[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
150 switch (origin) {
151 case SeekOrigin::SetOrigin:
152 default:
153 return SEEK_SET;
154 case SeekOrigin::CurrentPosition:
155 return SEEK_CUR;
156 case SeekOrigin::End:
157 return SEEK_END;
158 }
159}
160
161} // Anonymous namespace
162
163std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) {
164 if (!IsFile(path)) {
165 return "";
166 }
167
168 IOFile io_file{path, FileAccessMode::Read, type};
169
170 return io_file.ReadString(io_file.GetSize());
171}
172
173size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
174 std::string_view string) {
175 if (!IsFile(path)) {
176 return 0;
177 }
178
179 IOFile io_file{path, FileAccessMode::Write, type};
180
181 return io_file.WriteString(string);
182}
183
184size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
185 std::string_view string) {
186 if (!Exists(path)) {
187 return WriteStringToFile(path, type, string);
188 }
189
190 if (!IsFile(path)) {
191 return 0;
192 }
193
194 IOFile io_file{path, FileAccessMode::Append, type};
195
196 return io_file.WriteString(string);
197}
198
199IOFile::IOFile() = default;
200
201IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
202 Open(path, mode, type, flag);
203}
204
205IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
206 Open(path, mode, type, flag);
207}
208
209IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
210 Open(path, mode, type, flag);
211}
212
213IOFile::~IOFile() {
214 Close();
215}
216
217IOFile::IOFile(IOFile&& other) noexcept {
218 std::swap(file_path, other.file_path);
219 std::swap(file_access_mode, other.file_access_mode);
220 std::swap(file_type, other.file_type);
221 std::swap(file, other.file);
222}
223
224IOFile& IOFile::operator=(IOFile&& other) noexcept {
225 std::swap(file_path, other.file_path);
226 std::swap(file_access_mode, other.file_access_mode);
227 std::swap(file_type, other.file_type);
228 std::swap(file, other.file);
229 return *this;
230}
231
232fs::path IOFile::GetPath() const {
233 return file_path;
234}
235
236FileAccessMode IOFile::GetAccessMode() const {
237 return file_access_mode;
238}
239
240FileType IOFile::GetType() const {
241 return file_type;
242}
243
244void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
245 Close();
246
247 file_path = path;
248 file_access_mode = mode;
249 file_type = type;
250
251 errno = 0;
252
253#ifdef _WIN32
254 if (flag != FileShareFlag::ShareNone) {
255 file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
256 } else {
257 _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
258 }
259#else
260 file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
261#endif
262
263 if (!IsOpen()) {
264 const auto ec = std::error_code{errno, std::generic_category()};
265 LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
266 PathToUTF8String(file_path), ec.message());
267 }
268}
269
270void IOFile::Close() {
271 if (!IsOpen()) {
272 return;
273 }
274
275 errno = 0;
276
277 const auto close_result = std::fclose(file) == 0;
278
279 if (!close_result) {
280 const auto ec = std::error_code{errno, std::generic_category()};
281 LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
282 PathToUTF8String(file_path), ec.message());
283 }
284
285 file = nullptr;
286}
287
288bool IOFile::IsOpen() const {
289 return file != nullptr;
290}
291
292std::string IOFile::ReadString(size_t length) const {
293 std::vector<char> string_buffer(length);
294
295 const auto chars_read = ReadSpan<char>(string_buffer);
296 const auto string_size = chars_read != length ? chars_read : length;
297
298 return std::string{string_buffer.data(), string_size};
299}
300
301size_t IOFile::WriteString(std::span<const char> string) const {
302 return WriteSpan(string);
303}
304
305bool IOFile::Flush() const {
306 if (!IsOpen()) {
307 return false;
308 }
309
310 errno = 0;
311
312 const auto flush_result = std::fflush(file) == 0;
313
314 if (!flush_result) {
315 const auto ec = std::error_code{errno, std::generic_category()};
316 LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
317 PathToUTF8String(file_path), ec.message());
318 }
319
320 return flush_result;
321}
322
323bool IOFile::SetSize(u64 size) const {
324 if (!IsOpen()) {
325 return false;
326 }
327
328 errno = 0;
329
330#ifdef _WIN32
331 const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
332#else
333 const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
334#endif
335
336 if (!set_size_result) {
337 const auto ec = std::error_code{errno, std::generic_category()};
338 LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
339 PathToUTF8String(file_path), size, ec.message());
340 }
341
342 return set_size_result;
343}
344
345u64 IOFile::GetSize() const {
346 if (!IsOpen()) {
347 return 0;
348 }
349
350 std::error_code ec;
351
352 const auto file_size = fs::file_size(file_path, ec);
353
354 if (ec) {
355 LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
356 PathToUTF8String(file_path), ec.message());
357 return 0;
358 }
359
360 return file_size;
361}
362
363bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
364 if (!IsOpen()) {
365 return false;
366 }
367
368 errno = 0;
369
370 const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
371
372 if (!seek_result) {
373 const auto ec = std::error_code{errno, std::generic_category()};
374 LOG_ERROR(Common_Filesystem,
375 "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
376 PathToUTF8String(file_path), offset, origin, ec.message());
377 }
378
379 return seek_result;
380}
381
382s64 IOFile::Tell() const {
383 if (!IsOpen()) {
384 return 0;
385 }
386
387 errno = 0;
388
389 return ftello(file);
390}
391
392} // namespace Common::FS
diff --git a/src/common/fs/file.h b/src/common/fs/file.h
new file mode 100644
index 000000000..50e270c5b
--- /dev/null
+++ b/src/common/fs/file.h
@@ -0,0 +1,453 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstdio>
8#include <filesystem>
9#include <fstream>
10#include <span>
11#include <type_traits>
12#include <vector>
13
14#include "common/concepts.h"
15#include "common/fs/fs_types.h"
16#include "common/fs/fs_util.h"
17
18namespace Common::FS {
19
20enum class SeekOrigin {
21 SetOrigin, // Seeks from the start of the file.
22 CurrentPosition, // Seeks from the current file pointer position.
23 End, // Seeks from the end of the file.
24};
25
26/**
27 * Opens a file stream at path with the specified open mode.
28 *
29 * @param file_stream Reference to file stream
30 * @param path Filesystem path
31 * @param open_mode File stream open mode
32 */
33template <typename FileStream>
34void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
35 std::ios_base::openmode open_mode) {
36 file_stream.open(path, open_mode);
37}
38
39#ifdef _WIN32
40template <typename FileStream, typename Path>
41void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) {
42 if constexpr (IsChar<typename Path::value_type>) {
43 file_stream.open(ToU8String(path), open_mode);
44 } else {
45 file_stream.open(std::filesystem::path{path}, open_mode);
46 }
47}
48#endif
49
50/**
51 * Reads an entire file at path and returns a string of the contents read from the file.
52 * If the filesystem object at path is not a file, this function returns an empty string.
53 *
54 * @param path Filesystem path
55 * @param type File type
56 *
57 * @returns A string of the contents read from the file.
58 */
59[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type);
60
61#ifdef _WIN32
62template <typename Path>
63[[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) {
64 if constexpr (IsChar<typename Path::value_type>) {
65 return ReadStringFromFile(ToU8String(path), type);
66 } else {
67 return ReadStringFromFile(std::filesystem::path{path}, type);
68 }
69}
70#endif
71
72/**
73 * Writes a string to a file at path and returns the number of characters successfully written.
74 * If an file already exists at path, its contents will be erased.
75 * If the filesystem object at path is not a file, this function returns 0.
76 *
77 * @param path Filesystem path
78 * @param type File type
79 *
80 * @returns Number of characters successfully written.
81 */
82[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
83 std::string_view string);
84
85#ifdef _WIN32
86template <typename Path>
87[[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) {
88 if constexpr (IsChar<typename Path::value_type>) {
89 return WriteStringToFile(ToU8String(path), type, string);
90 } else {
91 return WriteStringToFile(std::filesystem::path{path}, type, string);
92 }
93}
94#endif
95
96/**
97 * Appends a string to a file at path and returns the number of characters successfully written.
98 * If a file does not exist at path, WriteStringToFile is called instead.
99 * If the filesystem object at path is not a file, this function returns 0.
100 *
101 * @param path Filesystem path
102 * @param type File type
103 *
104 * @returns Number of characters successfully written.
105 */
106[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
107 std::string_view string);
108
109#ifdef _WIN32
110template <typename Path>
111[[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) {
112 if constexpr (IsChar<typename Path::value_type>) {
113 return AppendStringToFile(ToU8String(path), type, string);
114 } else {
115 return AppendStringToFile(std::filesystem::path{path}, type, string);
116 }
117}
118#endif
119
120class IOFile final {
121public:
122 IOFile();
123
124 explicit IOFile(const std::string& path, FileAccessMode mode,
125 FileType type = FileType::BinaryFile,
126 FileShareFlag flag = FileShareFlag::ShareReadOnly);
127
128 explicit IOFile(std::string_view path, FileAccessMode mode,
129 FileType type = FileType::BinaryFile,
130 FileShareFlag flag = FileShareFlag::ShareReadOnly);
131
132 /**
133 * An IOFile is a lightweight wrapper on C Library file operations.
134 * Automatically closes an open file on the destruction of an IOFile object.
135 *
136 * @param path Filesystem path
137 * @param mode File access mode
138 * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
139 * @param flag (Windows only) File-share access flag, default is ShareReadOnly
140 */
141 explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
142 FileType type = FileType::BinaryFile,
143 FileShareFlag flag = FileShareFlag::ShareReadOnly);
144
145 ~IOFile();
146
147 IOFile(const IOFile&) = delete;
148 IOFile& operator=(const IOFile&) = delete;
149
150 IOFile(IOFile&& other) noexcept;
151 IOFile& operator=(IOFile&& other) noexcept;
152
153 /**
154 * Gets the path of the file.
155 *
156 * @returns The path of the file.
157 */
158 [[nodiscard]] std::filesystem::path GetPath() const;
159
160 /**
161 * Gets the access mode of the file.
162 *
163 * @returns The access mode of the file.
164 */
165 [[nodiscard]] FileAccessMode GetAccessMode() const;
166
167 /**
168 * Gets the type of the file.
169 *
170 * @returns The type of the file.
171 */
172 [[nodiscard]] FileType GetType() const;
173
174 /**
175 * Opens a file at path with the specified file access mode.
176 * This function behaves differently depending on the FileAccessMode.
177 * These behaviors are documented in each enum value of FileAccessMode.
178 *
179 * @param path Filesystem path
180 * @param mode File access mode
181 * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
182 * @param flag (Windows only) File-share access flag, default is ShareReadOnly
183 */
184 void Open(const std::filesystem::path& path, FileAccessMode mode,
185 FileType type = FileType::BinaryFile,
186 FileShareFlag flag = FileShareFlag::ShareReadOnly);
187
188#ifdef _WIN32
189 template <typename Path>
190 [[nodiscard]] void Open(const Path& path, FileAccessMode mode,
191 FileType type = FileType::BinaryFile,
192 FileShareFlag flag = FileShareFlag::ShareReadOnly) {
193 using ValueType = typename Path::value_type;
194 if constexpr (IsChar<ValueType>) {
195 Open(ToU8String(path), mode, type, flag);
196 } else {
197 Open(std::filesystem::path{path}, mode, type, flag);
198 }
199 }
200#endif
201
202 /// Closes the file if it is opened.
203 void Close();
204
205 /**
206 * Checks whether the file is open.
207 * Use this to check whether the calls to Open() or Close() succeeded.
208 *
209 * @returns True if the file is open, false otherwise.
210 */
211 [[nodiscard]] bool IsOpen() const;
212
213 /**
214 * Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
215 * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
216 * ReadObject and T must be a trivially copyable object.
217 *
218 * See ReadSpan for more details if T is a contiguous container.
219 * See ReadObject for more details if T is a trivially copyable object.
220 *
221 * @tparam T Contiguous container or trivially copyable object
222 *
223 * @param data Container of T::value_type data or reference to object
224 *
225 * @returns Count of T::value_type data or objects successfully read.
226 */
227 template <typename T>
228 [[nodiscard]] size_t Read(T& data) const {
229 if constexpr (IsSTLContainer<T>) {
230 using ContiguousType = typename T::value_type;
231 static_assert(std::is_trivially_copyable_v<ContiguousType>,
232 "Data type must be trivially copyable.");
233 return ReadSpan<ContiguousType>(data);
234 } else {
235 return ReadObject(data) ? 1 : 0;
236 }
237 }
238
239 /**
240 * Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
241 * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
242 * WriteObject and T must be a trivially copyable object.
243 *
244 * See WriteSpan for more details if T is a contiguous container.
245 * See WriteObject for more details if T is a trivially copyable object.
246 *
247 * @tparam T Contiguous container or trivially copyable object
248 *
249 * @param data Container of T::value_type data or const reference to object
250 *
251 * @returns Count of T::value_type data or objects successfully written.
252 */
253 template <typename T>
254 [[nodiscard]] size_t Write(const T& data) const {
255 if constexpr (IsSTLContainer<T>) {
256 using ContiguousType = typename T::value_type;
257 static_assert(std::is_trivially_copyable_v<ContiguousType>,
258 "Data type must be trivially copyable.");
259 return WriteSpan<ContiguousType>(data);
260 } else {
261 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
262 return WriteObject(data) ? 1 : 0;
263 }
264 }
265
266 /**
267 * Reads a span of T data from a file sequentially.
268 * This function reads from the current position of the file pointer and
269 * advances it by the (count of T * sizeof(T)) bytes successfully read.
270 *
271 * Failures occur when:
272 * - The file is not open
273 * - The opened file lacks read permissions
274 * - Attempting to read beyond the end-of-file
275 *
276 * @tparam T Data type
277 *
278 * @param data Span of T data
279 *
280 * @returns Count of T data successfully read.
281 */
282 template <typename T>
283 [[nodiscard]] size_t ReadSpan(std::span<T> data) const {
284 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
285
286 if (!IsOpen()) {
287 return 0;
288 }
289
290 return std::fread(data.data(), sizeof(T), data.size(), file);
291 }
292
293 /**
294 * Writes a span of T data to a file sequentially.
295 * This function writes from the current position of the file pointer and
296 * advances it by the (count of T * sizeof(T)) bytes successfully written.
297 *
298 * Failures occur when:
299 * - The file is not open
300 * - The opened file lacks write permissions
301 *
302 * @tparam T Data type
303 *
304 * @param data Span of T data
305 *
306 * @returns Count of T data successfully written.
307 */
308 template <typename T>
309 [[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
310 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
311
312 if (!IsOpen()) {
313 return 0;
314 }
315
316 return std::fwrite(data.data(), sizeof(T), data.size(), file);
317 }
318
319 /**
320 * Reads a T object from a file sequentially.
321 * This function reads from the current position of the file pointer and
322 * advances it by the sizeof(T) bytes successfully read.
323 *
324 * Failures occur when:
325 * - The file is not open
326 * - The opened file lacks read permissions
327 * - Attempting to read beyond the end-of-file
328 *
329 * @tparam T Data type
330 *
331 * @param object Reference to object
332 *
333 * @returns True if the object is successfully read from the file, false otherwise.
334 */
335 template <typename T>
336 [[nodiscard]] bool ReadObject(T& object) const {
337 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
338 static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
339
340 if (!IsOpen()) {
341 return false;
342 }
343
344 return std::fread(&object, sizeof(T), 1, file) == 1;
345 }
346
347 /**
348 * Writes a T object to a file sequentially.
349 * This function writes from the current position of the file pointer and
350 * advances it by the sizeof(T) bytes successfully written.
351 *
352 * Failures occur when:
353 * - The file is not open
354 * - The opened file lacks write permissions
355 *
356 * @tparam T Data type
357 *
358 * @param object Const reference to object
359 *
360 * @returns True if the object is successfully written to the file, false otherwise.
361 */
362 template <typename T>
363 [[nodiscard]] bool WriteObject(const T& object) const {
364 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
365 static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
366
367 if (!IsOpen()) {
368 return false;
369 }
370
371 return std::fwrite(&object, sizeof(T), 1, file) == 1;
372 }
373
374 /**
375 * Specialized function to read a string of a given length from a file sequentially.
376 * This function writes from the current position of the file pointer and
377 * advances it by the number of characters successfully read.
378 * The size of the returned string may not match length if not all bytes are successfully read.
379 *
380 * @param length Length of the string
381 *
382 * @returns A string read from the file.
383 */
384 [[nodiscard]] std::string ReadString(size_t length) const;
385
386 /**
387 * Specialized function to write a string to a file sequentially.
388 * This function writes from the current position of the file pointer and
389 * advances it by the number of characters successfully written.
390 *
391 * @param string Span of const char backed std::string or std::string_view
392 *
393 * @returns Number of characters successfully written.
394 */
395 [[nodiscard]] size_t WriteString(std::span<const char> string) const;
396
397 /**
398 * Flushes any unwritten buffered data into the file.
399 *
400 * @returns True if the flush was successful, false otherwise.
401 */
402 [[nodiscard]] bool Flush() const;
403
404 /**
405 * Resizes the file to a given size.
406 * If the file is resized to a smaller size, the remainder of the file is discarded.
407 * If the file is resized to a larger size, the new area appears as if zero-filled.
408 *
409 * Failures occur when:
410 * - The file is not open
411 *
412 * @param size File size in bytes
413 *
414 * @returns True if the file resize succeeded, false otherwise.
415 */
416 [[nodiscard]] bool SetSize(u64 size) const;
417
418 /**
419 * Gets the size of the file.
420 *
421 * Failures occur when:
422 * - The file is not open
423 *
424 * @returns The file size in bytes of the file. Returns 0 on failure.
425 */
426 [[nodiscard]] u64 GetSize() const;
427
428 /**
429 * Moves the current position of the file pointer with the specified offset and seek origin.
430 *
431 * @param offset Offset from seek origin
432 * @param origin Seek origin
433 *
434 * @returns True if the file pointer has moved to the specified offset, false otherwise.
435 */
436 [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
437
438 /**
439 * Gets the current position of the file pointer.
440 *
441 * @returns The current position of the file pointer.
442 */
443 [[nodiscard]] s64 Tell() const;
444
445private:
446 std::filesystem::path file_path;
447 FileAccessMode file_access_mode{};
448 FileType file_type{};
449
450 std::FILE* file = nullptr;
451};
452
453} // namespace Common::FS
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
new file mode 100644
index 000000000..d492480d9
--- /dev/null
+++ b/src/common/fs/fs.cpp
@@ -0,0 +1,610 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9
10namespace Common::FS {
11
12namespace fs = std::filesystem;
13
14// File Operations
15
16bool NewFile(const fs::path& path, u64 size) {
17 if (!ValidatePath(path)) {
18 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
19 return false;
20 }
21
22 if (!Exists(path.parent_path())) {
23 LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist",
24 PathToUTF8String(path));
25 return false;
26 }
27
28 if (Exists(path)) {
29 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path));
30 return false;
31 }
32
33 IOFile io_file{path, FileAccessMode::Write};
34
35 if (!io_file.IsOpen()) {
36 LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path));
37 return false;
38 }
39
40 if (!io_file.SetSize(size)) {
41 LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}",
42 PathToUTF8String(path), size);
43 return false;
44 }
45
46 io_file.Close();
47
48 LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}",
49 PathToUTF8String(path), size);
50
51 return true;
52}
53
54bool RemoveFile(const fs::path& path) {
55 if (!ValidatePath(path)) {
56 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
57 return false;
58 }
59
60 if (!Exists(path)) {
61 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
62 PathToUTF8String(path));
63 return true;
64 }
65
66 if (!IsFile(path)) {
67 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
68 PathToUTF8String(path));
69 return false;
70 }
71
72 std::error_code ec;
73
74 fs::remove(path, ec);
75
76 if (ec) {
77 LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}",
78 PathToUTF8String(path), ec.message());
79 return false;
80 }
81
82 LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}",
83 PathToUTF8String(path));
84
85 return true;
86}
87
88bool RenameFile(const fs::path& old_path, const fs::path& new_path) {
89 if (!ValidatePath(old_path) || !ValidatePath(new_path)) {
90 LOG_ERROR(Common_Filesystem,
91 "One or both input path(s) is not valid, old_path={}, new_path={}",
92 PathToUTF8String(old_path), PathToUTF8String(new_path));
93 return false;
94 }
95
96 if (!Exists(old_path)) {
97 LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist",
98 PathToUTF8String(old_path));
99 return false;
100 }
101
102 if (!IsFile(old_path)) {
103 LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file",
104 PathToUTF8String(old_path));
105 return false;
106 }
107
108 if (Exists(new_path)) {
109 LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists",
110 PathToUTF8String(new_path));
111 return false;
112 }
113
114 std::error_code ec;
115
116 fs::rename(old_path, new_path, ec);
117
118 if (ec) {
119 LOG_ERROR(Common_Filesystem,
120 "Failed to rename the file from old_path={} to new_path={}, ec_message={}",
121 PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message());
122 return false;
123 }
124
125 LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}",
126 PathToUTF8String(old_path), PathToUTF8String(new_path));
127
128 return true;
129}
130
131std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, FileType type,
132 FileShareFlag flag) {
133 if (!ValidatePath(path)) {
134 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
135 return nullptr;
136 }
137
138 if (!IsFile(path)) {
139 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file",
140 PathToUTF8String(path));
141 return nullptr;
142 }
143
144 auto io_file = std::make_shared<IOFile>(path, mode, type, flag);
145
146 if (!io_file->IsOpen()) {
147 io_file.reset();
148
149 LOG_ERROR(Common_Filesystem,
150 "Failed to open the file at path={} with mode={}, type={}, flag={}",
151 PathToUTF8String(path), mode, type, flag);
152
153 return nullptr;
154 }
155
156 LOG_DEBUG(Common_Filesystem,
157 "Successfully opened the file at path={} with mode={}, type={}, flag={}",
158 PathToUTF8String(path), mode, type, flag);
159
160 return io_file;
161}
162
163// Directory Operations
164
165bool CreateDir(const fs::path& path) {
166 if (!ValidatePath(path)) {
167 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
168 return false;
169 }
170
171 if (!Exists(path.parent_path())) {
172 LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist",
173 PathToUTF8String(path));
174 return false;
175 }
176
177 if (IsDir(path)) {
178 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory",
179 PathToUTF8String(path));
180 return true;
181 }
182
183 std::error_code ec;
184
185 fs::create_directory(path, ec);
186
187 if (ec) {
188 LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}",
189 PathToUTF8String(path), ec.message());
190 return false;
191 }
192
193 LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}",
194 PathToUTF8String(path));
195
196 return true;
197}
198
199bool CreateDirs(const fs::path& path) {
200 if (!ValidatePath(path)) {
201 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
202 return false;
203 }
204
205 if (IsDir(path)) {
206 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory",
207 PathToUTF8String(path));
208 return true;
209 }
210
211 std::error_code ec;
212
213 fs::create_directories(path, ec);
214
215 if (ec) {
216 LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}",
217 PathToUTF8String(path), ec.message());
218 return false;
219 }
220
221 LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}",
222 PathToUTF8String(path));
223
224 return true;
225}
226
227bool CreateParentDir(const fs::path& path) {
228 return CreateDir(path.parent_path());
229}
230
231bool CreateParentDirs(const fs::path& path) {
232 return CreateDirs(path.parent_path());
233}
234
235bool RemoveDir(const fs::path& path) {
236 if (!ValidatePath(path)) {
237 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
238 return false;
239 }
240
241 if (!Exists(path)) {
242 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
243 PathToUTF8String(path));
244 return true;
245 }
246
247 if (!IsDir(path)) {
248 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
249 PathToUTF8String(path));
250 return false;
251 }
252
253 std::error_code ec;
254
255 fs::remove(path, ec);
256
257 if (ec) {
258 LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}",
259 PathToUTF8String(path), ec.message());
260 return false;
261 }
262
263 LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}",
264 PathToUTF8String(path));
265
266 return true;
267}
268
269bool RemoveDirRecursively(const fs::path& path) {
270 if (!ValidatePath(path)) {
271 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
272 return false;
273 }
274
275 if (!Exists(path)) {
276 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
277 PathToUTF8String(path));
278 return true;
279 }
280
281 if (!IsDir(path)) {
282 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
283 PathToUTF8String(path));
284 return false;
285 }
286
287 std::error_code ec;
288
289 fs::remove_all(path, ec);
290
291 if (ec) {
292 LOG_ERROR(Common_Filesystem,
293 "Failed to remove the directory and its contents at path={}, ec_message={}",
294 PathToUTF8String(path), ec.message());
295 return false;
296 }
297
298 LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}",
299 PathToUTF8String(path));
300
301 return true;
302}
303
304bool RemoveDirContentsRecursively(const fs::path& path) {
305 if (!ValidatePath(path)) {
306 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
307 return false;
308 }
309
310 if (!Exists(path)) {
311 LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist",
312 PathToUTF8String(path));
313 return true;
314 }
315
316 if (!IsDir(path)) {
317 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
318 PathToUTF8String(path));
319 return false;
320 }
321
322 std::error_code ec;
323
324 for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
325 if (ec) {
326 LOG_ERROR(Common_Filesystem,
327 "Failed to completely enumerate the directory at path={}, ec_message={}",
328 PathToUTF8String(path), ec.message());
329 break;
330 }
331
332 fs::remove(entry.path(), ec);
333
334 if (ec) {
335 LOG_ERROR(Common_Filesystem,
336 "Failed to remove the filesystem object at path={}, ec_message={}",
337 PathToUTF8String(entry.path()), ec.message());
338 break;
339 }
340 }
341
342 if (ec) {
343 LOG_ERROR(Common_Filesystem,
344 "Failed to remove all the contents of the directory at path={}, ec_message={}",
345 PathToUTF8String(path), ec.message());
346 return false;
347 }
348
349 LOG_DEBUG(Common_Filesystem,
350 "Successfully removed all the contents of the directory at path={}",
351 PathToUTF8String(path));
352
353 return true;
354}
355
356bool RenameDir(const fs::path& old_path, const fs::path& new_path) {
357 if (!ValidatePath(old_path) || !ValidatePath(new_path)) {
358 LOG_ERROR(Common_Filesystem,
359 "One or both input path(s) is not valid, old_path={}, new_path={}",
360 PathToUTF8String(old_path), PathToUTF8String(new_path));
361 return false;
362 }
363
364 if (!Exists(old_path)) {
365 LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist",
366 PathToUTF8String(old_path));
367 return false;
368 }
369
370 if (!IsDir(old_path)) {
371 LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory",
372 PathToUTF8String(old_path));
373 return false;
374 }
375
376 if (Exists(new_path)) {
377 LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists",
378 PathToUTF8String(new_path));
379 return false;
380 }
381
382 std::error_code ec;
383
384 fs::rename(old_path, new_path, ec);
385
386 if (ec) {
387 LOG_ERROR(Common_Filesystem,
388 "Failed to rename the file from old_path={} to new_path={}, ec_message={}",
389 PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message());
390 return false;
391 }
392
393 LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}",
394 PathToUTF8String(old_path), PathToUTF8String(new_path));
395
396 return true;
397}
398
399void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback,
400 DirEntryFilter filter) {
401 if (!ValidatePath(path)) {
402 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
403 return;
404 }
405
406 if (!Exists(path)) {
407 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist",
408 PathToUTF8String(path));
409 return;
410 }
411
412 if (!IsDir(path)) {
413 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
414 PathToUTF8String(path));
415 return;
416 }
417
418 bool callback_error = false;
419
420 std::error_code ec;
421
422 for (const auto& entry : fs::directory_iterator(path, ec)) {
423 if (ec) {
424 break;
425 }
426
427 if (True(filter & DirEntryFilter::File) &&
428 entry.status().type() == fs::file_type::regular) {
429 if (!callback(entry.path())) {
430 callback_error = true;
431 break;
432 }
433 }
434
435 if (True(filter & DirEntryFilter::Directory) &&
436 entry.status().type() == fs::file_type::directory) {
437 if (!callback(entry.path())) {
438 callback_error = true;
439 break;
440 }
441 }
442 }
443
444 if (callback_error || ec) {
445 LOG_ERROR(Common_Filesystem,
446 "Failed to visit all the directory entries of path={}, ec_message={}",
447 PathToUTF8String(path), ec.message());
448 return;
449 }
450
451 LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}",
452 PathToUTF8String(path));
453}
454
455void IterateDirEntriesRecursively(const std::filesystem::path& path,
456 const DirEntryCallable& callback, DirEntryFilter filter) {
457 if (!ValidatePath(path)) {
458 LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path));
459 return;
460 }
461
462 if (!Exists(path)) {
463 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist",
464 PathToUTF8String(path));
465 return;
466 }
467
468 if (!IsDir(path)) {
469 LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory",
470 PathToUTF8String(path));
471 return;
472 }
473
474 bool callback_error = false;
475
476 std::error_code ec;
477
478 for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
479 if (ec) {
480 break;
481 }
482
483 if (True(filter & DirEntryFilter::File) &&
484 entry.status().type() == fs::file_type::regular) {
485 if (!callback(entry.path())) {
486 callback_error = true;
487 break;
488 }
489 }
490
491 if (True(filter & DirEntryFilter::Directory) &&
492 entry.status().type() == fs::file_type::directory) {
493 if (!callback(entry.path())) {
494 callback_error = true;
495 break;
496 }
497 }
498 }
499
500 if (callback_error || ec) {
501 LOG_ERROR(Common_Filesystem,
502 "Failed to visit all the directory entries of path={}, ec_message={}",
503 PathToUTF8String(path), ec.message());
504 return;
505 }
506
507 LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}",
508 PathToUTF8String(path));
509}
510
511// Generic Filesystem Operations
512
513bool Exists(const fs::path& path) {
514 return fs::exists(path);
515}
516
517bool IsFile(const fs::path& path) {
518 return fs::is_regular_file(path);
519}
520
521bool IsDir(const fs::path& path) {
522 return fs::is_directory(path);
523}
524
525fs::path GetCurrentDir() {
526 std::error_code ec;
527
528 const auto current_path = fs::current_path(ec);
529
530 if (ec) {
531 LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message());
532 return {};
533 }
534
535 return current_path;
536}
537
538bool SetCurrentDir(const fs::path& path) {
539 std::error_code ec;
540
541 fs::current_path(path, ec);
542
543 if (ec) {
544 LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}",
545 PathToUTF8String(path), ec.message());
546 return false;
547 }
548
549 return true;
550}
551
552fs::file_type GetEntryType(const fs::path& path) {
553 std::error_code ec;
554
555 const auto file_status = fs::status(path, ec);
556
557 if (ec) {
558 LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}",
559 PathToUTF8String(path), ec.message());
560 return fs::file_type::not_found;
561 }
562
563 return file_status.type();
564}
565
566u64 GetSize(const fs::path& path) {
567 std::error_code ec;
568
569 const auto file_size = fs::file_size(path, ec);
570
571 if (ec) {
572 LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
573 PathToUTF8String(path), ec.message());
574 return 0;
575 }
576
577 return file_size;
578}
579
580u64 GetFreeSpaceSize(const fs::path& path) {
581 std::error_code ec;
582
583 const auto space_info = fs::space(path, ec);
584
585 if (ec) {
586 LOG_ERROR(Common_Filesystem,
587 "Failed to retrieve the available free space of path={}, ec_message={}",
588 PathToUTF8String(path), ec.message());
589 return 0;
590 }
591
592 return space_info.free;
593}
594
595u64 GetTotalSpaceSize(const fs::path& path) {
596 std::error_code ec;
597
598 const auto space_info = fs::space(path, ec);
599
600 if (ec) {
601 LOG_ERROR(Common_Filesystem,
602 "Failed to retrieve the total capacity of path={}, ec_message={}",
603 PathToUTF8String(path), ec.message());
604 return 0;
605 }
606
607 return space_info.capacity;
608}
609
610} // namespace Common::FS
diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h
new file mode 100644
index 000000000..f6f256349
--- /dev/null
+++ b/src/common/fs/fs.h
@@ -0,0 +1,582 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <filesystem>
8#include <memory>
9
10#include "common/fs/fs_types.h"
11#include "common/fs/fs_util.h"
12
13namespace Common::FS {
14
15class IOFile;
16
17// File Operations
18
19/**
20 * Creates a new file at path with the specified size.
21 *
22 * Failures occur when:
23 * - Input path is not valid
24 * - The input path's parent directory does not exist
25 * - Filesystem object at path exists
26 * - Filesystem at path is read only
27 *
28 * @param path Filesystem path
29 * @param size File size
30 *
31 * @returns True if the file creation succeeds, false otherwise.
32 */
33[[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0);
34
35#ifdef _WIN32
36template <typename Path>
37[[nodiscard]] bool NewFile(const Path& path, u64 size = 0) {
38 if constexpr (IsChar<typename Path::value_type>) {
39 return NewFile(ToU8String(path), size);
40 } else {
41 return NewFile(std::filesystem::path{path}, size);
42 }
43}
44#endif
45
46/**
47 * Removes a file at path.
48 *
49 * Failures occur when:
50 * - Input path is not valid
51 * - Filesystem object at path is not a file
52 * - Filesystem at path is read only
53 *
54 * @param path Filesystem path
55 *
56 * @returns True if file removal succeeds or file does not exist, false otherwise.
57 */
58[[nodiscard]] bool RemoveFile(const std::filesystem::path& path);
59
60#ifdef _WIN32
61template <typename Path>
62[[nodiscard]] bool RemoveFile(const Path& path) {
63 if constexpr (IsChar<typename Path::value_type>) {
64 return RemoveFile(ToU8String(path));
65 } else {
66 return RemoveFile(std::filesystem::path{path});
67 }
68}
69#endif
70
71/**
72 * Renames a file from old_path to new_path.
73 *
74 * Failures occur when:
75 * - One or both input path(s) is not valid
76 * - Filesystem object at old_path does not exist
77 * - Filesystem object at old_path is not a file
78 * - Filesystem object at new_path exists
79 * - Filesystem at either path is read only
80 *
81 * @param old_path Old filesystem path
82 * @param new_path New filesystem path
83 *
84 * @returns True if file rename succeeds, false otherwise.
85 */
86[[nodiscard]] bool RenameFile(const std::filesystem::path& old_path,
87 const std::filesystem::path& new_path);
88
89#ifdef _WIN32
90template <typename Path1, typename Path2>
91[[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) {
92 using ValueType1 = typename Path1::value_type;
93 using ValueType2 = typename Path2::value_type;
94 if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
95 return RenameFile(ToU8String(old_path), ToU8String(new_path));
96 } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
97 return RenameFile(ToU8String(old_path), new_path);
98 } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
99 return RenameFile(old_path, ToU8String(new_path));
100 } else {
101 return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path});
102 }
103}
104#endif
105
106/**
107 * Opens a file at path with the specified file access mode.
108 * This function behaves differently depending on the FileAccessMode.
109 * These behaviors are documented in each enum value of FileAccessMode.
110 *
111 * Failures occur when:
112 * - Input path is not valid
113 * - Filesystem object at path is not a file
114 * - The file is not opened
115 *
116 * @param path Filesystem path
117 * @param mode File access mode
118 * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
119 * @param flag (Windows only) File-share access flag, default is ShareReadOnly
120 *
121 * @returns A shared pointer to the opened file. Returns nullptr on failure.
122 */
123[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const std::filesystem::path& path,
124 FileAccessMode mode,
125 FileType type = FileType::BinaryFile,
126 FileShareFlag flag = FileShareFlag::ShareReadOnly);
127
128#ifdef _WIN32
129template <typename Path>
130[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode,
131 FileType type = FileType::BinaryFile,
132 FileShareFlag flag = FileShareFlag::ShareReadOnly) {
133 if constexpr (IsChar<typename Path::value_type>) {
134 return FileOpen(ToU8String(path), mode, type, flag);
135 } else {
136 return FileOpen(std::filesystem::path{path}, mode, type, flag);
137 }
138}
139#endif
140
141// Directory Operations
142
143/**
144 * Creates a directory at path.
145 * Note that this function will *always* assume that the input path is a directory. For example,
146 * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt".
147 * If you intend to create the parent directory of a file, use CreateParentDir instead.
148 *
149 * Failures occur when:
150 * - Input path is not valid
151 * - The input path's parent directory does not exist
152 * - Filesystem at path is read only
153 *
154 * @param path Filesystem path
155 *
156 * @returns True if directory creation succeeds or directory already exists, false otherwise.
157 */
158[[nodiscard]] bool CreateDir(const std::filesystem::path& path);
159
160#ifdef _WIN32
161template <typename Path>
162[[nodiscard]] bool CreateDir(const Path& path) {
163 if constexpr (IsChar<typename Path::value_type>) {
164 return CreateDir(ToU8String(path));
165 } else {
166 return CreateDir(std::filesystem::path{path});
167 }
168}
169#endif
170
171/**
172 * Recursively creates a directory at path.
173 * Note that this function will *always* assume that the input path is a directory. For example,
174 * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt".
175 * If you intend to create the parent directory of a file, use CreateParentDirs instead.
176 * Unlike CreateDir, this creates all of input path's parent directories if they do not exist.
177 *
178 * Failures occur when:
179 * - Input path is not valid
180 * - Filesystem at path is read only
181 *
182 * @param path Filesystem path
183 *
184 * @returns True if directory creation succeeds or directory already exists, false otherwise.
185 */
186[[nodiscard]] bool CreateDirs(const std::filesystem::path& path);
187
188#ifdef _WIN32
189template <typename Path>
190[[nodiscard]] bool CreateDirs(const Path& path) {
191 if constexpr (IsChar<typename Path::value_type>) {
192 return CreateDirs(ToU8String(path));
193 } else {
194 return CreateDirs(std::filesystem::path{path});
195 }
196}
197#endif
198
199/**
200 * Creates the parent directory of a given path.
201 * This function calls CreateDir(path.parent_path()), see CreateDir for more details.
202 *
203 * @param path Filesystem path
204 *
205 * @returns True if directory creation succeeds or directory already exists, false otherwise.
206 */
207[[nodiscard]] bool CreateParentDir(const std::filesystem::path& path);
208
209#ifdef _WIN32
210template <typename Path>
211[[nodiscard]] bool CreateParentDir(const Path& path) {
212 if constexpr (IsChar<typename Path::value_type>) {
213 return CreateParentDir(ToU8String(path));
214 } else {
215 return CreateParentDir(std::filesystem::path{path});
216 }
217}
218#endif
219
220/**
221 * Recursively creates the parent directory of a given path.
222 * This function calls CreateDirs(path.parent_path()), see CreateDirs for more details.
223 *
224 * @param path Filesystem path
225 *
226 * @returns True if directory creation succeeds or directory already exists, false otherwise.
227 */
228[[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path);
229
230#ifdef _WIN32
231template <typename Path>
232[[nodiscard]] bool CreateParentDirs(const Path& path) {
233 if constexpr (IsChar<typename Path::value_type>) {
234 return CreateParentDirs(ToU8String(path));
235 } else {
236 return CreateParentDirs(std::filesystem::path{path});
237 }
238}
239#endif
240
241/**
242 * Removes a directory at path.
243 *
244 * Failures occur when:
245 * - Input path is not valid
246 * - Filesystem object at path is not a directory
247 * - The given directory is not empty
248 * - Filesystem at path is read only
249 *
250 * @param path Filesystem path
251 *
252 * @returns True if directory removal succeeds or directory does not exist, false otherwise.
253 */
254[[nodiscard]] bool RemoveDir(const std::filesystem::path& path);
255
256#ifdef _WIN32
257template <typename Path>
258[[nodiscard]] bool RemoveDir(const Path& path) {
259 if constexpr (IsChar<typename Path::value_type>) {
260 return RemoveDir(ToU8String(path));
261 } else {
262 return RemoveDir(std::filesystem::path{path});
263 }
264}
265#endif
266
267/**
268 * Removes all the contents within the given directory and removes the directory itself.
269 *
270 * Failures occur when:
271 * - Input path is not valid
272 * - Filesystem object at path is not a directory
273 * - Filesystem at path is read only
274 *
275 * @param path Filesystem path
276 *
277 * @returns True if the directory and all of its contents are removed successfully, false otherwise.
278 */
279[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path);
280
281#ifdef _WIN32
282template <typename Path>
283[[nodiscard]] bool RemoveDirRecursively(const Path& path) {
284 if constexpr (IsChar<typename Path::value_type>) {
285 return RemoveDirRecursively(ToU8String(path));
286 } else {
287 return RemoveDirRecursively(std::filesystem::path{path});
288 }
289}
290#endif
291
292/**
293 * Removes all the contents within the given directory without removing the directory itself.
294 *
295 * Failures occur when:
296 * - Input path is not valid
297 * - Filesystem object at path is not a directory
298 * - Filesystem at path is read only
299 *
300 * @param path Filesystem path
301 *
302 * @returns True if all of the directory's contents are removed successfully, false otherwise.
303 */
304[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path);
305
306#ifdef _WIN32
307template <typename Path>
308[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) {
309 if constexpr (IsChar<typename Path::value_type>) {
310 return RemoveDirContentsRecursively(ToU8String(path));
311 } else {
312 return RemoveDirContentsRecursively(std::filesystem::path{path});
313 }
314}
315#endif
316
317/**
318 * Renames a directory from old_path to new_path.
319 *
320 * Failures occur when:
321 * - One or both input path(s) is not valid
322 * - Filesystem object at old_path does not exist
323 * - Filesystem object at old_path is not a directory
324 * - Filesystem object at new_path exists
325 * - Filesystem at either path is read only
326 *
327 * @param old_path Old filesystem path
328 * @param new_path New filesystem path
329 *
330 * @returns True if directory rename succeeds, false otherwise.
331 */
332[[nodiscard]] bool RenameDir(const std::filesystem::path& old_path,
333 const std::filesystem::path& new_path);
334
335#ifdef _WIN32
336template <typename Path1, typename Path2>
337[[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) {
338 using ValueType1 = typename Path1::value_type;
339 using ValueType2 = typename Path2::value_type;
340 if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
341 return RenameDir(ToU8String(old_path), ToU8String(new_path));
342 } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
343 return RenameDir(ToU8String(old_path), new_path);
344 } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
345 return RenameDir(old_path, ToU8String(new_path));
346 } else {
347 return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path});
348 }
349}
350#endif
351
352/**
353 * Iterates over the directory entries of a given directory.
354 * This does not iterate over the sub-directories of the given directory.
355 * The DirEntryCallable callback is called for each visited directory entry.
356 * A filter can be set to control which directory entries are visited based on their type.
357 * By default, both files and directories are visited.
358 * If the callback returns false or there is an error, the iteration is immediately halted.
359 *
360 * Failures occur when:
361 * - Input path is not valid
362 * - Filesystem object at path is not a directory
363 *
364 * @param path Filesystem path
365 * @param callback Callback to be called for each visited directory entry
366 * @param filter Directory entry type filter
367 */
368void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback,
369 DirEntryFilter filter = DirEntryFilter::All);
370
371#ifdef _WIN32
372template <typename Path>
373void IterateDirEntries(const Path& path, const DirEntryCallable& callback,
374 DirEntryFilter filter = DirEntryFilter::All) {
375 if constexpr (IsChar<typename Path::value_type>) {
376 IterateDirEntries(ToU8String(path), callback, filter);
377 } else {
378 IterateDirEntries(std::filesystem::path{path}, callback, filter);
379 }
380}
381#endif
382
383/**
384 * Iterates over the directory entries of a given directory and its sub-directories.
385 * The DirEntryCallable callback is called for each visited directory entry.
386 * A filter can be set to control which directory entries are visited based on their type.
387 * By default, both files and directories are visited.
388 * If the callback returns false or there is an error, the iteration is immediately halted.
389 *
390 * Failures occur when:
391 * - Input path is not valid
392 * - Filesystem object at path does not exist
393 * - Filesystem object at path is not a directory
394 *
395 * @param path Filesystem path
396 * @param callback Callback to be called for each visited directory entry
397 * @param filter Directory entry type filter
398 */
399void IterateDirEntriesRecursively(const std::filesystem::path& path,
400 const DirEntryCallable& callback,
401 DirEntryFilter filter = DirEntryFilter::All);
402
403#ifdef _WIN32
404template <typename Path>
405void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback,
406 DirEntryFilter filter = DirEntryFilter::All) {
407 if constexpr (IsChar<typename Path::value_type>) {
408 IterateDirEntriesRecursively(ToU8String(path), callback, filter);
409 } else {
410 IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter);
411 }
412}
413#endif
414
415// Generic Filesystem Operations
416
417/**
418 * Returns whether a filesystem object at path exists.
419 *
420 * @param path Filesystem path
421 *
422 * @returns True if a filesystem object at path exists, false otherwise.
423 */
424[[nodiscard]] bool Exists(const std::filesystem::path& path);
425
426#ifdef _WIN32
427template <typename Path>
428[[nodiscard]] bool Exists(const Path& path) {
429 if constexpr (IsChar<typename Path::value_type>) {
430 return Exists(ToU8String(path));
431 } else {
432 return Exists(std::filesystem::path{path});
433 }
434}
435#endif
436
437/**
438 * Returns whether a filesystem object at path is a file.
439 *
440 * @param path Filesystem path
441 *
442 * @returns True if a filesystem object at path is a file, false otherwise.
443 */
444[[nodiscard]] bool IsFile(const std::filesystem::path& path);
445
446#ifdef _WIN32
447template <typename Path>
448[[nodiscard]] bool IsFile(const Path& path) {
449 if constexpr (IsChar<typename Path::value_type>) {
450 return IsFile(ToU8String(path));
451 } else {
452 return IsFile(std::filesystem::path{path});
453 }
454}
455#endif
456
457/**
458 * Returns whether a filesystem object at path is a directory.
459 *
460 * @param path Filesystem path
461 *
462 * @returns True if a filesystem object at path is a directory, false otherwise.
463 */
464[[nodiscard]] bool IsDir(const std::filesystem::path& path);
465
466#ifdef _WIN32
467template <typename Path>
468[[nodiscard]] bool IsDir(const Path& path) {
469 if constexpr (IsChar<typename Path::value_type>) {
470 return IsDir(ToU8String(path));
471 } else {
472 return IsDir(std::filesystem::path{path});
473 }
474}
475#endif
476
477/**
478 * Gets the current working directory.
479 *
480 * @returns The current working directory. Returns an empty path on failure.
481 */
482[[nodiscard]] std::filesystem::path GetCurrentDir();
483
484/**
485 * Sets the current working directory to path.
486 *
487 * @returns True if the current working directory is successfully set, false otherwise.
488 */
489[[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path);
490
491#ifdef _WIN32
492template <typename Path>
493[[nodiscard]] bool SetCurrentDir(const Path& path) {
494 if constexpr (IsChar<typename Path::value_type>) {
495 return SetCurrentDir(ToU8String(path));
496 } else {
497 return SetCurrentDir(std::filesystem::path{path});
498 }
499}
500#endif
501
502/**
503 * Gets the entry type of the filesystem object at path.
504 *
505 * @param path Filesystem path
506 *
507 * @returns The entry type of the filesystem object. Returns file_type::not_found on failure.
508 */
509[[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path);
510
511#ifdef _WIN32
512template <typename Path>
513[[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) {
514 if constexpr (IsChar<typename Path::value_type>) {
515 return GetEntryType(ToU8String(path));
516 } else {
517 return GetEntryType(std::filesystem::path{path});
518 }
519}
520#endif
521
522/**
523 * Gets the size of the filesystem object at path.
524 *
525 * @param path Filesystem path
526 *
527 * @returns The size in bytes of the filesystem object. Returns 0 on failure.
528 */
529[[nodiscard]] u64 GetSize(const std::filesystem::path& path);
530
531#ifdef _WIN32
532template <typename Path>
533[[nodiscard]] u64 GetSize(const Path& path) {
534 if constexpr (IsChar<typename Path::value_type>) {
535 return GetSize(ToU8String(path));
536 } else {
537 return GetSize(std::filesystem::path{path});
538 }
539}
540#endif
541
542/**
543 * Gets the free space size of the filesystem at path.
544 *
545 * @param path Filesystem path
546 *
547 * @returns The free space size in bytes of the filesystem at path. Returns 0 on failure.
548 */
549[[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path);
550
551#ifdef _WIN32
552template <typename Path>
553[[nodiscard]] u64 GetFreeSpaceSize(const Path& path) {
554 if constexpr (IsChar<typename Path::value_type>) {
555 return GetFreeSpaceSize(ToU8String(path));
556 } else {
557 return GetFreeSpaceSize(std::filesystem::path{path});
558 }
559}
560#endif
561
562/**
563 * Gets the total capacity of the filesystem at path.
564 *
565 * @param path Filesystem path
566 *
567 * @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure.
568 */
569[[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path);
570
571#ifdef _WIN32
572template <typename Path>
573[[nodiscard]] u64 GetTotalSpaceSize(const Path& path) {
574 if constexpr (IsChar<typename Path::value_type>) {
575 return GetTotalSpaceSize(ToU8String(path));
576 } else {
577 return GetTotalSpaceSize(std::filesystem::path{path});
578 }
579}
580#endif
581
582} // namespace Common::FS
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
new file mode 100644
index 000000000..b32614797
--- /dev/null
+++ b/src/common/fs/fs_paths.h
@@ -0,0 +1,27 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7// yuzu data directories
8
9#define YUZU_DIR "yuzu"
10#define PORTABLE_DIR "user"
11
12// Sub-directories contained within a yuzu data directory
13
14#define CACHE_DIR "cache"
15#define CONFIG_DIR "config"
16#define DUMP_DIR "dump"
17#define KEYS_DIR "keys"
18#define LOAD_DIR "load"
19#define LOG_DIR "log"
20#define NAND_DIR "nand"
21#define SCREENSHOTS_DIR "screenshots"
22#define SDMC_DIR "sdmc"
23#define SHADER_DIR "shader"
24
25// yuzu-specific files
26
27#define LOG_FILE "yuzu_log.txt"
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h
new file mode 100644
index 000000000..089980aee
--- /dev/null
+++ b/src/common/fs/fs_types.h
@@ -0,0 +1,73 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11
12namespace Common::FS {
13
14enum class FileAccessMode {
15 /**
16 * If the file at path exists, it opens the file for reading.
17 * If the file at path does not exist, it fails to open the file.
18 */
19 Read = 1 << 0,
20 /**
21 * If the file at path exists, the existing contents of the file are erased.
22 * The empty file is then opened for writing.
23 * If the file at path does not exist, it creates and opens a new empty file for writing.
24 */
25 Write = 1 << 1,
26 /**
27 * If the file at path exists, it opens the file for reading and writing.
28 * If the file at path does not exist, it fails to open the file.
29 */
30 ReadWrite = Read | Write,
31 /**
32 * If the file at path exists, it opens the file for appending.
33 * If the file at path does not exist, it creates and opens a new empty file for appending.
34 */
35 Append = 1 << 2,
36 /**
37 * If the file at path exists, it opens the file for both reading and appending.
38 * If the file at path does not exist, it creates and opens a new empty file for both
39 * reading and appending.
40 */
41 ReadAppend = Read | Append,
42};
43
44enum class FileType {
45 BinaryFile,
46 TextFile,
47};
48
49enum class FileShareFlag {
50 ShareNone, // Provides exclusive access to the file.
51 ShareReadOnly, // Provides read only shared access to the file.
52 ShareWriteOnly, // Provides write only shared access to the file.
53 ShareReadWrite, // Provides read and write shared access to the file.
54};
55
56enum class DirEntryFilter {
57 File = 1 << 0,
58 Directory = 1 << 1,
59 All = File | Directory,
60};
61DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
62
63/**
64 * A callback function which takes in the path of a directory entry.
65 *
66 * @param path The path of a directory entry
67 *
68 * @returns A boolean value.
69 * Return true to indicate whether the callback is successful, false otherwise.
70 */
71using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>;
72
73} // namespace Common::FS
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp
new file mode 100644
index 000000000..0ddfc3131
--- /dev/null
+++ b/src/common/fs/fs_util.cpp
@@ -0,0 +1,13 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/fs/fs_util.h"
6
7namespace Common::FS {
8
9std::u8string ToU8String(std::string_view utf8_string) {
10 return std::u8string{utf8_string.begin(), utf8_string.end()};
11}
12
13} // namespace Common::FS
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h
new file mode 100644
index 000000000..951df53b6
--- /dev/null
+++ b/src/common/fs/fs_util.h
@@ -0,0 +1,25 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <concepts>
8#include <string>
9#include <string_view>
10
11namespace Common::FS {
12
13template <typename T>
14concept IsChar = std::same_as<T, char>;
15
16/**
17 * Converts a UTF-8 encoded std::string or std::string_view to a std::u8string.
18 *
19 * @param utf8_string UTF-8 encoded string
20 *
21 * @returns UTF-8 encoded std::u8string.
22 */
23[[nodiscard]] std::u8string ToU8String(std::string_view utf8_string);
24
25} // namespace Common::FS
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
new file mode 100644
index 000000000..8b732a21c
--- /dev/null
+++ b/src/common/fs/path_util.cpp
@@ -0,0 +1,432 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <unordered_map>
7
8#include "common/fs/fs.h"
9#include "common/fs/fs_paths.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12
13#ifdef _WIN32
14#include <shlobj.h> // Used in GetExeDirectory()
15#else
16#include <cstdlib> // Used in Get(Home/Data)Directory()
17#include <pwd.h> // Used in GetHomeDirectory()
18#include <sys/types.h> // Used in GetHomeDirectory()
19#include <unistd.h> // Used in GetDataDirectory()
20#endif
21
22#ifdef __APPLE__
23#include <sys/param.h> // Used in GetBundleDirectory()
24
25// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
26// ignore them if we're not using clang. The macro is only used to prevent linking against
27// functions that don't exist on older versions of macOS, and the worst case scenario is a linker
28// error, so this is perfectly safe, just inconvenient.
29#ifndef __clang__
30#define availability(...)
31#endif
32#include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory()
33#include <CoreFoundation/CFString.h> // Used in GetBundleDirectory()
34#include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory()
35#ifdef availability
36#undef availability
37#endif
38#endif
39
40#ifndef MAX_PATH
41#ifdef _WIN32
42// This is the maximum number of UTF-16 code units permissible in Windows file paths
43#define MAX_PATH 260
44#else
45// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
46#define MAX_PATH 1024
47#endif
48#endif
49
50namespace Common::FS {
51
52namespace fs = std::filesystem;
53
54/**
55 * The PathManagerImpl is a singleton allowing to manage the mapping of
56 * YuzuPath enums to real filesystem paths.
57 * This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl.
58 * These are used by GetYuzuPath and SetYuzuPath respectively to get or modify
59 * the path mapped by the YuzuPath enum.
60 */
61class PathManagerImpl {
62public:
63 static PathManagerImpl& GetInstance() {
64 static PathManagerImpl path_manager_impl;
65
66 return path_manager_impl;
67 }
68
69 PathManagerImpl(const PathManagerImpl&) = delete;
70 PathManagerImpl& operator=(const PathManagerImpl&) = delete;
71
72 PathManagerImpl(PathManagerImpl&&) = delete;
73 PathManagerImpl& operator=(PathManagerImpl&&) = delete;
74
75 [[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) {
76 return yuzu_paths.at(yuzu_path);
77 }
78
79 void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) {
80 yuzu_paths.insert_or_assign(yuzu_path, new_path);
81 }
82
83private:
84 PathManagerImpl() {
85#ifdef _WIN32
86 auto yuzu_path = GetExeDirectory() / PORTABLE_DIR;
87
88 if (!IsDir(yuzu_path)) {
89 yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
90 }
91
92 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
93 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
94 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
95#else
96 auto yuzu_path = GetCurrentDir() / PORTABLE_DIR;
97
98 if (Exists(yuzu_path) && IsDir(yuzu_path)) {
99 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
100 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR);
101 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR);
102 } else {
103 yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR;
104
105 GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
106 GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR);
107 GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR);
108 }
109#endif
110
111 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
112 GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
113 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
114 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
115 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
116 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
117 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
118 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
119 }
120
121 ~PathManagerImpl() = default;
122
123 void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
124 void(FS::CreateDir(new_path));
125
126 SetYuzuPathImpl(yuzu_path, new_path);
127 }
128
129 std::unordered_map<YuzuPath, fs::path> yuzu_paths;
130};
131
132std::string PathToUTF8String(const fs::path& path) {
133 const auto utf8_string = path.u8string();
134
135 return std::string{utf8_string.begin(), utf8_string.end()};
136}
137
138bool ValidatePath(const fs::path& path) {
139 if (path.empty()) {
140 LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
141 return false;
142 }
143
144#ifdef _WIN32
145 if (path.u16string().size() >= MAX_PATH) {
146 LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
147 return false;
148 }
149#else
150 if (path.u8string().size() >= MAX_PATH) {
151 LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
152 return false;
153 }
154#endif
155
156 return true;
157}
158
159fs::path ConcatPath(const fs::path& first, const fs::path& second) {
160 const bool second_has_dir_sep = IsDirSeparator(second.u8string().front());
161
162 if (!second_has_dir_sep) {
163 return (first / second).lexically_normal();
164 }
165
166 fs::path concat_path = first;
167 concat_path += second;
168
169 return concat_path.lexically_normal();
170}
171
172fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) {
173 const auto concatenated_path = ConcatPath(base, offset);
174
175 if (!IsPathSandboxed(base, concatenated_path)) {
176 return base;
177 }
178
179 return concatenated_path;
180}
181
182bool IsPathSandboxed(const fs::path& base, const fs::path& path) {
183 const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string();
184 const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string();
185
186 if (path_string.size() < base_string.size()) {
187 return false;
188 }
189
190 return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0;
191}
192
193bool IsDirSeparator(char character) {
194 return character == '/' || character == '\\';
195}
196
197bool IsDirSeparator(char8_t character) {
198 return character == u8'/' || character == u8'\\';
199}
200
201fs::path RemoveTrailingSeparators(const fs::path& path) {
202 if (path.empty()) {
203 return path;
204 }
205
206 auto string_path = path.u8string();
207
208 while (IsDirSeparator(string_path.back())) {
209 string_path.pop_back();
210 }
211
212 return fs::path{string_path};
213}
214
215const fs::path& GetYuzuPath(YuzuPath yuzu_path) {
216 return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path);
217}
218
219std::string GetYuzuPathString(YuzuPath yuzu_path) {
220 return PathToUTF8String(GetYuzuPath(yuzu_path));
221}
222
223void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
224 if (!FS::IsDir(new_path)) {
225 LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
226 PathToUTF8String(new_path));
227 return;
228 }
229
230 PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path);
231}
232
233#ifdef _WIN32
234
235fs::path GetExeDirectory() {
236 wchar_t exe_path[MAX_PATH];
237
238 GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
239
240 if (!exe_path) {
241 LOG_ERROR(Common_Filesystem,
242 "Failed to get the path to the executable of the current process");
243 }
244
245 return fs::path{exe_path}.parent_path();
246}
247
248fs::path GetAppDataRoamingDirectory() {
249 PWSTR appdata_roaming_path = nullptr;
250
251 SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path);
252
253 auto fs_appdata_roaming_path = fs::path{appdata_roaming_path};
254
255 CoTaskMemFree(appdata_roaming_path);
256
257 if (fs_appdata_roaming_path.empty()) {
258 LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory");
259 }
260
261 return fs_appdata_roaming_path;
262}
263
264#else
265
266fs::path GetHomeDirectory() {
267 const char* home_env_var = getenv("HOME");
268
269 if (home_env_var) {
270 return fs::path{home_env_var};
271 }
272
273 LOG_INFO(Common_Filesystem,
274 "$HOME is not defined in the environment variables, "
275 "attempting to query passwd to get the home path of the current user");
276
277 const auto* pw = getpwuid(getuid());
278
279 if (!pw) {
280 LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user");
281 return {};
282 }
283
284 return fs::path{pw->pw_dir};
285}
286
287fs::path GetDataDirectory(const std::string& env_name) {
288 const char* data_env_var = getenv(env_name.c_str());
289
290 if (data_env_var) {
291 return fs::path{data_env_var};
292 }
293
294 if (env_name == "XDG_DATA_HOME") {
295 return GetHomeDirectory() / ".local/share";
296 } else if (env_name == "XDG_CACHE_HOME") {
297 return GetHomeDirectory() / ".cache";
298 } else if (env_name == "XDG_CONFIG_HOME") {
299 return GetHomeDirectory() / ".config";
300 }
301
302 return {};
303}
304
305#endif
306
307#ifdef __APPLE__
308
309fs::path GetBundleDirectory() {
310 char app_bundle_path[MAXPATHLEN];
311
312 // Get the main bundle for the app
313 CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle());
314 CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle);
315
316 CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path));
317
318 CFRelease(bundle_ref);
319 CFRelease(bundle_path);
320
321 return fs::path{app_bundle_path};
322}
323
324#endif
325
326// vvvvvvvvvv Deprecated vvvvvvvvvv //
327
328std::string_view RemoveTrailingSlash(std::string_view path) {
329 if (path.empty()) {
330 return path;
331 }
332
333 if (path.back() == '\\' || path.back() == '/') {
334 path.remove_suffix(1);
335 return path;
336 }
337
338 return path;
339}
340
341std::vector<std::string> SplitPathComponents(std::string_view filename) {
342 std::string copy(filename);
343 std::replace(copy.begin(), copy.end(), '\\', '/');
344 std::vector<std::string> out;
345
346 std::stringstream stream(copy);
347 std::string item;
348 while (std::getline(stream, item, '/')) {
349 out.push_back(std::move(item));
350 }
351
352 return out;
353}
354
355std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
356 std::string path(path_);
357 char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
358 char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
359
360 if (directory_separator == DirectorySeparator::PlatformDefault) {
361#ifdef _WIN32
362 type1 = '/';
363 type2 = '\\';
364#endif
365 }
366
367 std::replace(path.begin(), path.end(), type1, type2);
368
369 auto start = path.begin();
370#ifdef _WIN32
371 // allow network paths which start with a double backslash (e.g. \\server\share)
372 if (start != path.end())
373 ++start;
374#endif
375 path.erase(std::unique(start, path.end(),
376 [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
377 path.end());
378 return std::string(RemoveTrailingSlash(path));
379}
380
381std::string_view GetParentPath(std::string_view path) {
382 const auto name_bck_index = path.rfind('\\');
383 const auto name_fwd_index = path.rfind('/');
384 std::size_t name_index;
385
386 if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
387 name_index = std::min(name_bck_index, name_fwd_index);
388 } else {
389 name_index = std::max(name_bck_index, name_fwd_index);
390 }
391
392 return path.substr(0, name_index);
393}
394
395std::string_view GetPathWithoutTop(std::string_view path) {
396 if (path.empty()) {
397 return path;
398 }
399
400 while (path[0] == '\\' || path[0] == '/') {
401 path.remove_prefix(1);
402 if (path.empty()) {
403 return path;
404 }
405 }
406
407 const auto name_bck_index = path.find('\\');
408 const auto name_fwd_index = path.find('/');
409 return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
410}
411
412std::string_view GetFilename(std::string_view path) {
413 const auto name_index = path.find_last_of("\\/");
414
415 if (name_index == std::string_view::npos) {
416 return {};
417 }
418
419 return path.substr(name_index + 1);
420}
421
422std::string_view GetExtensionFromFilename(std::string_view name) {
423 const std::size_t index = name.rfind('.');
424
425 if (index == std::string_view::npos) {
426 return {};
427 }
428
429 return name.substr(index + 1);
430}
431
432} // namespace Common::FS
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
new file mode 100644
index 000000000..a9fadbceb
--- /dev/null
+++ b/src/common/fs/path_util.h
@@ -0,0 +1,309 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <filesystem>
8#include <vector>
9
10#include "common/fs/fs_util.h"
11
12namespace Common::FS {
13
14enum class YuzuPath {
15 YuzuDir, // Where yuzu stores its data.
16 CacheDir, // Where cached filesystem data is stored.
17 ConfigDir, // Where config files are stored.
18 DumpDir, // Where dumped data is stored.
19 KeysDir, // Where key files are stored.
20 LoadDir, // Where cheat/mod files are stored.
21 LogDir, // Where log files are stored.
22 NANDDir, // Where the emulated NAND is stored.
23 ScreenshotsDir, // Where yuzu screenshots are stored.
24 SDMCDir, // Where the emulated SDMC is stored.
25 ShaderDir, // Where shaders are stored.
26};
27
28/**
29 * Converts a filesystem path to a UTF-8 encoded std::string.
30 *
31 * @param path Filesystem path
32 *
33 * @returns UTF-8 encoded std::string.
34 */
35[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
36
37/**
38 * Validates a given path.
39 *
40 * A given path is valid if it meets these conditions:
41 * - The path is not empty
42 * - The path is not too long
43 *
44 * @param path Filesystem path
45 *
46 * @returns True if the path is valid, false otherwise.
47 */
48[[nodiscard]] bool ValidatePath(const std::filesystem::path& path);
49
50#ifdef _WIN32
51template <typename Path>
52[[nodiscard]] bool ValidatePath(const Path& path) {
53 if constexpr (IsChar<typename Path::value_type>) {
54 return ValidatePath(ToU8String(path));
55 } else {
56 return ValidatePath(std::filesystem::path{path});
57 }
58}
59#endif
60
61/**
62 * Concatenates two filesystem paths together.
63 *
64 * This is needed since the following occurs when using std::filesystem::path's operator/:
65 * first: "/first/path"
66 * second: "/second/path" (Note that the second path has a directory separator in the front)
67 * first / second yields "/second/path" when the desired result is first/path/second/path
68 *
69 * @param first First filesystem path
70 * @param second Second filesystem path
71 *
72 * @returns A concatenated filesystem path.
73 */
74[[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first,
75 const std::filesystem::path& second);
76
77#ifdef _WIN32
78template <typename Path1, typename Path2>
79[[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) {
80 using ValueType1 = typename Path1::value_type;
81 using ValueType2 = typename Path2::value_type;
82 if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
83 return ConcatPath(ToU8String(first), ToU8String(second));
84 } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
85 return ConcatPath(ToU8String(first), second);
86 } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
87 return ConcatPath(first, ToU8String(second));
88 } else {
89 return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second});
90 }
91}
92#endif
93
94/**
95 * Safe variant of ConcatPath that takes in a base path and an offset path from the given base path.
96 *
97 * If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path,
98 * this will return the concatenated path. Otherwise this will return the base path.
99 *
100 * @param base Base filesystem path
101 * @param offset Offset filesystem path
102 *
103 * @returns A concatenated filesystem path if it is within the base path,
104 * returns the base path otherwise.
105 */
106[[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base,
107 const std::filesystem::path& offset);
108
109#ifdef _WIN32
110template <typename Path1, typename Path2>
111[[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) {
112 using ValueType1 = typename Path1::value_type;
113 using ValueType2 = typename Path2::value_type;
114 if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
115 return ConcatPathSafe(ToU8String(base), ToU8String(offset));
116 } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
117 return ConcatPathSafe(ToU8String(base), offset);
118 } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
119 return ConcatPathSafe(base, ToU8String(offset));
120 } else {
121 return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset});
122 }
123}
124#endif
125
126/**
127 * Checks whether a given path is sandboxed within a given base path.
128 *
129 * @param base Base filesystem path
130 * @param path Filesystem path
131 *
132 * @returns True if the given path is sandboxed within the given base path, false otherwise.
133 */
134[[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base,
135 const std::filesystem::path& path);
136
137#ifdef _WIN32
138template <typename Path1, typename Path2>
139[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) {
140 using ValueType1 = typename Path1::value_type;
141 using ValueType2 = typename Path2::value_type;
142 if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
143 return IsPathSandboxed(ToU8String(base), ToU8String(path));
144 } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
145 return IsPathSandboxed(ToU8String(base), path);
146 } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
147 return IsPathSandboxed(base, ToU8String(path));
148 } else {
149 return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path});
150 }
151}
152#endif
153
154/**
155 * Checks if a character is a directory separator (either a forward slash or backslash).
156 *
157 * @param character Character
158 *
159 * @returns True if the character is a directory separator, false otherwise.
160 */
161[[nodiscard]] bool IsDirSeparator(char character);
162
163/**
164 * Checks if a character is a directory separator (either a forward slash or backslash).
165 *
166 * @param character Character
167 *
168 * @returns True if the character is a directory separator, false otherwise.
169 */
170[[nodiscard]] bool IsDirSeparator(char8_t character);
171
172/**
173 * Removes any trailing directory separators if they exist in the given path.
174 *
175 * @param path Filesystem path
176 *
177 * @returns The filesystem path without any trailing directory separators.
178 */
179[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path);
180
181#ifdef _WIN32
182template <typename Path>
183[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) {
184 if constexpr (IsChar<typename Path::value_type>) {
185 return RemoveTrailingSeparators(ToU8String(path));
186 } else {
187 return RemoveTrailingSeparators(std::filesystem::path{path});
188 }
189}
190#endif
191
192/**
193 * Gets the filesystem path associated with the YuzuPath enum.
194 *
195 * @param yuzu_path YuzuPath enum
196 *
197 * @returns The filesystem path associated with the YuzuPath enum.
198 */
199[[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path);
200
201/**
202 * Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
203 *
204 * @param yuzu_path YuzuPath enum
205 *
206 * @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
207 */
208[[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path);
209
210/**
211 * Sets a new filesystem path associated with the YuzuPath enum.
212 * If the filesystem object at new_path is not a directory, this function will not do anything.
213 *
214 * @param yuzu_path YuzuPath enum
215 * @param new_path New filesystem path
216 */
217void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path);
218
219#ifdef _WIN32
220template <typename Path>
221[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
222 if constexpr (IsChar<typename Path::value_type>) {
223 SetYuzuPath(yuzu_path, ToU8String(new_path));
224 } else {
225 SetYuzuPath(yuzu_path, std::filesystem::path{new_path});
226 }
227}
228#endif
229
230#ifdef _WIN32
231
232/**
233 * Gets the path of the directory containing the executable of the current process.
234 *
235 * @returns The path of the directory containing the executable of the current process.
236 */
237[[nodiscard]] std::filesystem::path GetExeDirectory();
238
239/**
240 * Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming).
241 *
242 * @returns The path of the current user's %APPDATA% directory.
243 */
244[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory();
245
246#else
247
248/**
249 * Gets the path of the directory specified by the #HOME environment variable.
250 * If $HOME is not defined, it will attempt to query the user database in passwd instead.
251 *
252 * @returns The path of the current user's home directory.
253 */
254[[nodiscard]] std::filesystem::path GetHomeDirectory();
255
256/**
257 * Gets the relevant paths for yuzu to store its data based on the given XDG environment variable.
258 * See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
259 * Defaults to $HOME/.local/share for main application data,
260 * $HOME/.cache for cached data, and $HOME/.config for configuration files.
261 *
262 * @param env_name XDG environment variable name
263 *
264 * @returns The path where yuzu should store its data.
265 */
266[[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name);
267
268#endif
269
270#ifdef __APPLE__
271
272[[nodiscard]] std::filesystem::path GetBundleDirectory();
273
274#endif
275
276// vvvvvvvvvv Deprecated vvvvvvvvvv //
277
278// Removes the final '/' or '\' if one exists
279[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
280
281enum class DirectorySeparator {
282 ForwardSlash,
283 BackwardSlash,
284 PlatformDefault,
285};
286
287// Splits the path on '/' or '\' and put the components into a vector
288// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
289[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
290
291// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
292// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
293[[nodiscard]] std::string SanitizePath(
294 std::string_view path,
295 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
296
297// Gets all of the text up to the last '/' or '\' in the path.
298[[nodiscard]] std::string_view GetParentPath(std::string_view path);
299
300// Gets all of the text after the first '/' or '\' in the path.
301[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
302
303// Gets the filename of the path
304[[nodiscard]] std::string_view GetFilename(std::string_view path);
305
306// Gets the extension of the filename
307[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
308
309} // namespace Common::FS