summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Morph2021-05-25 19:32:56 -0400
committerGravatar GitHub2021-05-25 19:32:56 -0400
commit065867e2c24e9856c360fc2d6b9a86c92aedc43e (patch)
tree7964e85ef4f01a3c2b8f44e850f37b384405b930
parentMerge pull request #6349 from german77/suppress_config_warning (diff)
downloadyuzu-065867e2c24e9856c360fc2d6b9a86c92aedc43e.tar.gz
yuzu-065867e2c24e9856c360fc2d6b9a86c92aedc43e.tar.xz
yuzu-065867e2c24e9856c360fc2d6b9a86c92aedc43e.zip
common: fs: Rework the Common Filesystem interface to make use of std::filesystem (#6270)
* common: fs: fs_types: Create filesystem types Contains various filesystem types used by the Common::FS library * common: fs: fs_util: Add std::string to std::u8string conversion utility * common: fs: path_util: Add utlity functions for paths Contains various utility functions for getting or manipulating filesystem paths used by the Common::FS library * common: fs: file: Rewrite the IOFile implementation * common: fs: Reimplement Common::FS library using std::filesystem * common: fs: fs_paths: Add fs_paths to replace common_paths * common: fs: path_util: Add the rest of the path functions * common: Remove the previous Common::FS implementation * general: Remove unused fs includes * string_util: Remove unused function and include * nvidia_flags: Migrate to the new Common::FS library * settings: Migrate to the new Common::FS library * logging: backend: Migrate to the new Common::FS library * core: Migrate to the new Common::FS library * perf_stats: Migrate to the new Common::FS library * reporter: Migrate to the new Common::FS library * telemetry_session: Migrate to the new Common::FS library * key_manager: Migrate to the new Common::FS library * bis_factory: Migrate to the new Common::FS library * registered_cache: Migrate to the new Common::FS library * xts_archive: Migrate to the new Common::FS library * service: acc: Migrate to the new Common::FS library * applets/profile: Migrate to the new Common::FS library * applets/web: Migrate to the new Common::FS library * service: filesystem: Migrate to the new Common::FS library * loader: Migrate to the new Common::FS library * gl_shader_disk_cache: Migrate to the new Common::FS library * nsight_aftermath_tracker: Migrate to the new Common::FS library * vulkan_library: Migrate to the new Common::FS library * configure_debug: Migrate to the new Common::FS library * game_list_worker: Migrate to the new Common::FS library * config: Migrate to the new Common::FS library * configure_filesystem: Migrate to the new Common::FS library * configure_per_game_addons: Migrate to the new Common::FS library * configure_profile_manager: Migrate to the new Common::FS library * configure_ui: Migrate to the new Common::FS library * input_profiles: Migrate to the new Common::FS library * yuzu_cmd: config: Migrate to the new Common::FS library * yuzu_cmd: Migrate to the new Common::FS library * vfs_real: Migrate to the new Common::FS library * vfs: Migrate to the new Common::FS library * vfs_libzip: Migrate to the new Common::FS library * service: bcat: Migrate to the new Common::FS library * yuzu: main: Migrate to the new Common::FS library * vfs_real: Delete the contents of an existing file in CreateFile Current usages of CreateFile expect to delete the contents of an existing file, retain this behavior for now. * input_profiles: Don't iterate the input profile dir if it does not exist Silences an error produced in the log if the directory does not exist. * game_list_worker: Skip parsing file if the returned VfsFile is nullptr Prevents crashes in GetLoader when the virtual file is nullptr * common: fs: Validate paths for path length * service: filesystem: Open the mod load directory as read only
-rw-r--r--src/common/CMakeLists.txt13
-rw-r--r--src/common/common_paths.h52
-rw-r--r--src/common/file_util.cpp1032
-rw-r--r--src/common/file_util.h298
-rw-r--r--src/common/fs/file.cpp392
-rw-r--r--src/common/fs/file.h450
-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
-rw-r--r--src/common/logging/backend.cpp27
-rw-r--r--src/common/logging/backend.h5
-rw-r--r--src/common/nvidia_flags.cpp24
-rw-r--r--src/common/settings.cpp16
-rw-r--r--src/common/string_util.cpp13
-rw-r--r--src/common/string_util.h2
-rw-r--r--src/core/core.cpp4
-rw-r--r--src/core/crypto/key_manager.cpp139
-rw-r--r--src/core/crypto/key_manager.h6
-rw-r--r--src/core/file_sys/bis_factory.cpp4
-rw-r--r--src/core/file_sys/mode.h8
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp1
-rw-r--r--src/core/file_sys/registered_cache.cpp2
-rw-r--r--src/core/file_sys/vfs.cpp8
-rw-r--r--src/core/file_sys/vfs_libzip.cpp1
-rw-r--r--src/core/file_sys/vfs_real.cpp245
-rw-r--r--src/core/file_sys/vfs_real.h4
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/hle/service/acc/acc.cpp28
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp33
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp47
-rw-r--r--src/core/hle/service/am/applets/web_browser.h5
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp68
-rw-r--r--src/core/hle/service/fatal/fatal.cpp1
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp21
-rw-r--r--src/core/hle/service/mii/manager.cpp1
-rw-r--r--src/core/hle/service/ns/pl_u.cpp2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp1
-rw-r--r--src/core/loader/elf.cpp1
-rw-r--r--src/core/loader/loader.cpp2
-rw-r--r--src/core/loader/nca.cpp1
-rw-r--r--src/core/loader/nro.cpp1
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/perf_stats.cpp19
-rw-r--r--src/core/reporter.cpp23
-rw-r--r--src/core/telemetry_session.cpp47
-rw-r--r--src/tests/core/core_timing.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp153
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h11
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp47
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp8
-rw-r--r--src/yuzu/applets/profile_select.cpp9
-rw-r--r--src/yuzu/applets/web_browser.cpp24
-rw-r--r--src/yuzu/configuration/config.cpp98
-rw-r--r--src/yuzu/configuration/configure_debug.cpp4
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp39
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp2
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp8
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp13
-rw-r--r--src/yuzu/configuration/configure_system.cpp1
-rw-r--r--src/yuzu/configuration/configure_ui.cpp23
-rw-r--r--src/yuzu/configuration/input_profiles.cpp58
-rw-r--r--src/yuzu/game_list_worker.cpp56
-rw-r--r--src/yuzu/game_list_worker.h2
-rw-r--r--src/yuzu/main.cpp219
-rw-r--r--src/yuzu_cmd/config.cpp47
-rw-r--r--src/yuzu_cmd/config.h3
-rw-r--r--src/yuzu_cmd/yuzu.cpp11
74 files changed, 3789 insertions, 2173 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 88644eeb6..eafb96b0b 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -109,7 +109,6 @@ add_library(common STATIC
109 cityhash.cpp 109 cityhash.cpp
110 cityhash.h 110 cityhash.h
111 common_funcs.h 111 common_funcs.h
112 common_paths.h
113 common_sizes.h 112 common_sizes.h
114 common_types.h 113 common_types.h
115 concepts.h 114 concepts.h
@@ -118,8 +117,16 @@ add_library(common STATIC
118 dynamic_library.h 117 dynamic_library.h
119 fiber.cpp 118 fiber.cpp
120 fiber.h 119 fiber.h
121 file_util.cpp 120 fs/file.cpp
122 file_util.h 121 fs/file.h
122 fs/fs.cpp
123 fs/fs.h
124 fs/fs_paths.h
125 fs/fs_types.h
126 fs/fs_util.cpp
127 fs/fs_util.h
128 fs/path_util.cpp
129 fs/path_util.h
123 hash.h 130 hash.h
124 hex_util.cpp 131 hex_util.cpp
125 hex_util.h 132 hex_util.h
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
deleted file mode 100644
index 3c593d5f6..000000000
--- a/src/common/common_paths.h
+++ /dev/null
@@ -1,52 +0,0 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7// Directory separators, do we need this?
8#define DIR_SEP "/"
9#define DIR_SEP_CHR '/'
10
11#ifndef MAX_PATH
12#define MAX_PATH 260
13#endif
14
15// The user data dir
16#define ROOT_DIR "."
17#define USERDATA_DIR "user"
18#ifdef USER_DIR
19#define EMU_DATA_DIR USER_DIR
20#else
21#define EMU_DATA_DIR "yuzu"
22#endif
23
24// Dirs in both User and Sys
25#define EUR_DIR "EUR"
26#define USA_DIR "USA"
27#define JAP_DIR "JAP"
28
29// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir)
30#define CONFIG_DIR "config"
31#define CACHE_DIR "cache"
32#define SDMC_DIR "sdmc"
33#define NAND_DIR "nand"
34#define SYSDATA_DIR "sysdata"
35#define KEYS_DIR "keys"
36#define LOAD_DIR "load"
37#define DUMP_DIR "dump"
38#define SCREENSHOTS_DIR "screenshots"
39#define SHADER_DIR "shader"
40#define LOG_DIR "log"
41
42// Filenames
43// Files in the directory returned by GetUserPath(UserPath::ConfigDir)
44#define EMU_CONFIG "emu.ini"
45#define DEBUGGER_CONFIG "debugger.ini"
46#define LOGGER_CONFIG "logger.ini"
47// Files in the directory returned by GetUserPath(UserPath::LogDir)
48#define LOG_FILE "yuzu_log.txt"
49
50// Sys files
51#define SHARED_FONT "shared_font.bin"
52#define AES_KEYS "aes_keys.txt"
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
deleted file mode 100644
index 18fbfa25b..000000000
--- a/src/common/file_util.cpp
+++ /dev/null
@@ -1,1032 +0,0 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <limits>
7#include <memory>
8#include <sstream>
9#include <unordered_map>
10#include "common/assert.h"
11#include "common/common_funcs.h"
12#include "common/common_paths.h"
13#include "common/file_util.h"
14#include "common/logging/log.h"
15
16#ifdef _WIN32
17#include <windows.h>
18// windows.h needs to be included before other windows headers
19#include <direct.h> // getcwd
20#include <io.h>
21#include <shellapi.h>
22#include <shlobj.h> // for SHGetFolderPath
23#include <tchar.h>
24#include "common/string_util.h"
25
26#ifdef _MSC_VER
27// 64 bit offsets for MSVC
28#define fseeko _fseeki64
29#define ftello _ftelli64
30#define fileno _fileno
31#endif
32
33// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64
34#define stat _stat64
35#define fstat _fstat64
36
37#else
38#ifdef __APPLE__
39#include <sys/param.h>
40#endif
41#include <cctype>
42#include <cerrno>
43#include <cstdlib>
44#include <cstring>
45#include <dirent.h>
46#include <pwd.h>
47#include <unistd.h>
48#endif
49
50#if defined(__APPLE__)
51// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
52// ignore them if we're not using clang. The macro is only used to prevent linking against
53// functions that don't exist on older versions of macOS, and the worst case scenario is a linker
54// error, so this is perfectly safe, just inconvenient.
55#ifndef __clang__
56#define availability(...)
57#endif
58#include <CoreFoundation/CFBundle.h>
59#include <CoreFoundation/CFString.h>
60#include <CoreFoundation/CFURL.h>
61#ifdef availability
62#undef availability
63#endif
64
65#endif
66
67#include <algorithm>
68#include <sys/stat.h>
69
70#ifndef S_ISDIR
71#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
72#endif
73
74// This namespace has various generic functions related to files and paths.
75// The code still needs a ton of cleanup.
76// REMEMBER: strdup considered harmful!
77namespace Common::FS {
78
79// Remove any ending forward slashes from directory paths
80// Modifies argument.
81static void StripTailDirSlashes(std::string& fname) {
82 if (fname.length() <= 1) {
83 return;
84 }
85
86 std::size_t i = fname.length();
87 while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
88 --i;
89 }
90 fname.resize(i);
91}
92
93bool Exists(const std::string& filename) {
94 struct stat file_info;
95
96 std::string copy(filename);
97 StripTailDirSlashes(copy);
98
99#ifdef _WIN32
100 // Windows needs a slash to identify a driver root
101 if (copy.size() != 0 && copy.back() == ':')
102 copy += DIR_SEP_CHR;
103
104 int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
105#else
106 int result = stat(copy.c_str(), &file_info);
107#endif
108
109 return (result == 0);
110}
111
112bool IsDirectory(const std::string& filename) {
113 struct stat file_info;
114
115 std::string copy(filename);
116 StripTailDirSlashes(copy);
117
118#ifdef _WIN32
119 // Windows needs a slash to identify a driver root
120 if (copy.size() != 0 && copy.back() == ':')
121 copy += DIR_SEP_CHR;
122
123 int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
124#else
125 int result = stat(copy.c_str(), &file_info);
126#endif
127
128 if (result < 0) {
129 LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
130 return false;
131 }
132
133 return S_ISDIR(file_info.st_mode);
134}
135
136bool Delete(const std::string& filename) {
137 LOG_TRACE(Common_Filesystem, "file {}", filename);
138
139 // Return true because we care about the file no
140 // being there, not the actual delete.
141 if (!Exists(filename)) {
142 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
143 return true;
144 }
145
146 // We can't delete a directory
147 if (IsDirectory(filename)) {
148 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
149 return false;
150 }
151
152#ifdef _WIN32
153 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
154 LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
155 return false;
156 }
157#else
158 if (unlink(filename.c_str()) == -1) {
159 LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
160 return false;
161 }
162#endif
163
164 return true;
165}
166
167bool CreateDir(const std::string& path) {
168 LOG_TRACE(Common_Filesystem, "directory {}", path);
169#ifdef _WIN32
170 if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
171 return true;
172 DWORD error = GetLastError();
173 if (error == ERROR_ALREADY_EXISTS) {
174 LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
175 return true;
176 }
177 LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
178 return false;
179#else
180 if (mkdir(path.c_str(), 0755) == 0)
181 return true;
182
183 int err = errno;
184
185 if (err == EEXIST) {
186 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
187 return true;
188 }
189
190 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
191 return false;
192#endif
193}
194
195bool CreateFullPath(const std::string& fullPath) {
196 int panicCounter = 100;
197 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
198
199 if (Exists(fullPath)) {
200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
201 return true;
202 }
203
204 std::size_t position = 0;
205 while (true) {
206 // Find next sub path
207 position = fullPath.find(DIR_SEP_CHR, position);
208
209 // we're done, yay!
210 if (position == fullPath.npos)
211 return true;
212
213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
214 std::string const subPath(fullPath.substr(0, position + 1));
215 if (!IsDirectory(subPath) && !CreateDir(subPath)) {
216 LOG_ERROR(Common, "CreateFullPath: directory creation failed");
217 return false;
218 }
219
220 // A safety check
221 panicCounter--;
222 if (panicCounter <= 0) {
223 LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
224 return false;
225 }
226 position++;
227 }
228}
229
230bool DeleteDir(const std::string& filename) {
231 LOG_TRACE(Common_Filesystem, "directory {}", filename);
232
233 // check if a directory
234 if (!IsDirectory(filename)) {
235 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
236 return false;
237 }
238
239#ifdef _WIN32
240 if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
241 return true;
242#else
243 if (rmdir(filename.c_str()) == 0)
244 return true;
245#endif
246 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
247
248 return false;
249}
250
251bool Rename(const std::string& srcFilename, const std::string& destFilename) {
252 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
253#ifdef _WIN32
254 if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
255 Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
256 return true;
257#else
258 if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
259 return true;
260#endif
261 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
262 GetLastErrorMsg());
263 return false;
264}
265
266bool Copy(const std::string& srcFilename, const std::string& destFilename) {
267 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
268#ifdef _WIN32
269 if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
270 Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
271 return true;
272
273 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
274 GetLastErrorMsg());
275 return false;
276#else
277 using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
278
279 // Open input file
280 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
281 if (!input) {
282 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
283 destFilename, GetLastErrorMsg());
284 return false;
285 }
286
287 // open output file
288 CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
289 if (!output) {
290 LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
291 destFilename, GetLastErrorMsg());
292 return false;
293 }
294
295 // copy loop
296 std::array<char, 1024> buffer;
297 while (!feof(input.get())) {
298 // read input
299 std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
300 if (rnum != buffer.size()) {
301 if (ferror(input.get()) != 0) {
302 LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
303 srcFilename, destFilename, GetLastErrorMsg());
304 return false;
305 }
306 }
307
308 // write output
309 std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
310 if (wnum != rnum) {
311 LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
312 destFilename, GetLastErrorMsg());
313 return false;
314 }
315 }
316
317 return true;
318#endif
319}
320
321u64 GetSize(const std::string& filename) {
322 if (!Exists(filename)) {
323 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
324 return 0;
325 }
326
327 if (IsDirectory(filename)) {
328 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
329 return 0;
330 }
331
332 struct stat buf;
333#ifdef _WIN32
334 if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
335#else
336 if (stat(filename.c_str(), &buf) == 0)
337#endif
338 {
339 LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
340 return buf.st_size;
341 }
342
343 LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
344 return 0;
345}
346
347u64 GetSize(const int fd) {
348 struct stat buf;
349 if (fstat(fd, &buf) != 0) {
350 LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
351 return 0;
352 }
353 return buf.st_size;
354}
355
356u64 GetSize(FILE* f) {
357 // can't use off_t here because it can be 32-bit
358 u64 pos = ftello(f);
359 if (fseeko(f, 0, SEEK_END) != 0) {
360 LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
361 return 0;
362 }
363 u64 size = ftello(f);
364 if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
365 LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
366 return 0;
367 }
368 return size;
369}
370
371bool CreateEmptyFile(const std::string& filename) {
372 LOG_TRACE(Common_Filesystem, "{}", filename);
373
374 if (!IOFile(filename, "wb").IsOpen()) {
375 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
376 return false;
377 }
378
379 return true;
380}
381
382bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
383 DirectoryEntryCallable callback) {
384 LOG_TRACE(Common_Filesystem, "directory {}", directory);
385
386 // How many files + directories we found
387 u64 found_entries = 0;
388
389 // Save the status of callback function
390 bool callback_error = false;
391
392#ifdef _WIN32
393 // Find the first file in the directory.
394 WIN32_FIND_DATAW ffd;
395
396 HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd);
397 if (handle_find == INVALID_HANDLE_VALUE) {
398 FindClose(handle_find);
399 return false;
400 }
401 // windows loop
402 do {
403 const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
404#else
405 DIR* dirp = opendir(directory.c_str());
406 if (!dirp)
407 return false;
408
409 // non windows loop
410 while (struct dirent* result = readdir(dirp)) {
411 const std::string virtual_name(result->d_name);
412#endif
413
414 if (virtual_name == "." || virtual_name == "..")
415 continue;
416
417 u64 ret_entries = 0;
418 if (!callback(&ret_entries, directory, virtual_name)) {
419 callback_error = true;
420 break;
421 }
422 found_entries += ret_entries;
423
424#ifdef _WIN32
425 } while (FindNextFileW(handle_find, &ffd) != 0);
426 FindClose(handle_find);
427#else
428 }
429 closedir(dirp);
430#endif
431
432 if (callback_error)
433 return false;
434
435 // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
436 if (num_entries_out != nullptr)
437 *num_entries_out = found_entries;
438 return true;
439}
440
441u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
442 unsigned int recursion) {
443 const auto callback = [recursion, &parent_entry](u64* num_entries_out,
444 const std::string& directory,
445 const std::string& virtual_name) -> bool {
446 FSTEntry entry;
447 entry.virtualName = virtual_name;
448 entry.physicalName = directory + DIR_SEP + virtual_name;
449
450 if (IsDirectory(entry.physicalName)) {
451 entry.isDirectory = true;
452 // is a directory, lets go inside if we didn't recurse to often
453 if (recursion > 0) {
454 entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
455 *num_entries_out += entry.size;
456 } else {
457 entry.size = 0;
458 }
459 } else { // is a file
460 entry.isDirectory = false;
461 entry.size = GetSize(entry.physicalName);
462 }
463 (*num_entries_out)++;
464
465 // Push into the tree
466 parent_entry.children.push_back(std::move(entry));
467 return true;
468 };
469
470 u64 num_entries;
471 return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
472}
473
474bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
475 const auto callback = [recursion](u64*, const std::string& directory,
476 const std::string& virtual_name) {
477 const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
478
479 if (IsDirectory(new_path)) {
480 if (recursion == 0) {
481 return false;
482 }
483 return DeleteDirRecursively(new_path, recursion - 1);
484 }
485 return Delete(new_path);
486 };
487
488 if (!ForeachDirectoryEntry(nullptr, directory, callback))
489 return false;
490
491 // Delete the outermost directory
492 DeleteDir(directory);
493 return true;
494}
495
496void CopyDir([[maybe_unused]] const std::string& source_path,
497 [[maybe_unused]] const std::string& dest_path) {
498#ifndef _WIN32
499 if (source_path == dest_path) {
500 return;
501 }
502 if (!Exists(source_path)) {
503 return;
504 }
505 if (!Exists(dest_path)) {
506 CreateFullPath(dest_path);
507 }
508
509 DIR* dirp = opendir(source_path.c_str());
510 if (!dirp) {
511 return;
512 }
513
514 while (struct dirent* result = readdir(dirp)) {
515 const std::string virtualName(result->d_name);
516 // check for "." and ".."
517 if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
518 ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
519 continue;
520 }
521
522 std::string source, dest;
523 source = source_path + virtualName;
524 dest = dest_path + virtualName;
525 if (IsDirectory(source)) {
526 source += '/';
527 dest += '/';
528 if (!Exists(dest)) {
529 CreateFullPath(dest);
530 }
531 CopyDir(source, dest);
532 } else if (!Exists(dest)) {
533 Copy(source, dest);
534 }
535 }
536 closedir(dirp);
537#endif
538}
539
540std::optional<std::string> GetCurrentDir() {
541// Get the current working directory (getcwd uses malloc)
542#ifdef _WIN32
543 wchar_t* dir = _wgetcwd(nullptr, 0);
544 if (!dir) {
545#else
546 char* dir = getcwd(nullptr, 0);
547 if (!dir) {
548#endif
549 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
550 return std::nullopt;
551 }
552#ifdef _WIN32
553 std::string strDir = Common::UTF16ToUTF8(dir);
554#else
555 std::string strDir = dir;
556#endif
557 free(dir);
558 return strDir;
559}
560
561bool SetCurrentDir(const std::string& directory) {
562#ifdef _WIN32
563 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
564#else
565 return chdir(directory.c_str()) == 0;
566#endif
567}
568
569#if defined(__APPLE__)
570std::string GetBundleDirectory() {
571 CFURLRef BundleRef;
572 char AppBundlePath[MAXPATHLEN];
573 // Get the main bundle for the app
574 BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
575 CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
576 CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
577 CFRelease(BundleRef);
578 CFRelease(BundlePath);
579
580 return AppBundlePath;
581}
582#endif
583
584#ifdef _WIN32
585const std::string& GetExeDirectory() {
586 static std::string exe_path;
587 if (exe_path.empty()) {
588 wchar_t wchar_exe_path[2048];
589 GetModuleFileNameW(nullptr, wchar_exe_path, 2048);
590 exe_path = Common::UTF16ToUTF8(wchar_exe_path);
591 exe_path = exe_path.substr(0, exe_path.find_last_of('\\'));
592 }
593 return exe_path;
594}
595
596std::string AppDataRoamingDirectory() {
597 PWSTR pw_local_path = nullptr;
598 // Only supported by Windows Vista or later
599 SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path);
600 std::string local_path = Common::UTF16ToUTF8(pw_local_path);
601 CoTaskMemFree(pw_local_path);
602 return local_path;
603}
604#else
605/**
606 * @return The user’s home directory on POSIX systems
607 */
608static const std::string& GetHomeDirectory() {
609 static std::string home_path;
610 if (home_path.empty()) {
611 const char* envvar = getenv("HOME");
612 if (envvar) {
613 home_path = envvar;
614 } else {
615 auto pw = getpwuid(getuid());
616 ASSERT_MSG(pw,
617 "$HOME isn’t defined, and the current user can’t be found in /etc/passwd.");
618 home_path = pw->pw_dir;
619 }
620 }
621 return home_path;
622}
623
624/**
625 * Follows the XDG Base Directory Specification to get a directory path
626 * @param envvar The XDG environment variable to get the value from
627 * @return The directory path
628 * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
629 */
630static const std::string GetUserDirectory(const std::string& envvar) {
631 const char* directory = getenv(envvar.c_str());
632
633 std::string user_dir;
634 if (directory) {
635 user_dir = directory;
636 } else {
637 std::string subdirectory;
638 if (envvar == "XDG_DATA_HOME")
639 subdirectory = DIR_SEP ".local" DIR_SEP "share";
640 else if (envvar == "XDG_CONFIG_HOME")
641 subdirectory = DIR_SEP ".config";
642 else if (envvar == "XDG_CACHE_HOME")
643 subdirectory = DIR_SEP ".cache";
644 else
645 ASSERT_MSG(false, "Unknown XDG variable {}.", envvar);
646 user_dir = GetHomeDirectory() + subdirectory;
647 }
648
649 ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar);
650 ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar);
651
652 return user_dir;
653}
654#endif
655
656std::string GetSysDirectory() {
657 std::string sysDir;
658
659#if defined(__APPLE__)
660 sysDir = GetBundleDirectory();
661 sysDir += DIR_SEP;
662 sysDir += SYSDATA_DIR;
663#else
664 sysDir = SYSDATA_DIR;
665#endif
666 sysDir += DIR_SEP;
667
668 LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
669 return sysDir;
670}
671
672const std::string& GetUserPath(UserPath path, const std::string& new_path) {
673 static std::unordered_map<UserPath, std::string> paths;
674 auto& user_path = paths[UserPath::UserDir];
675
676 // Set up all paths and files on the first run
677 if (user_path.empty()) {
678#ifdef _WIN32
679 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
680 if (!IsDirectory(user_path)) {
681 user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
682 } else {
683 LOG_INFO(Common_Filesystem, "Using the local user directory");
684 }
685
686 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
687 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
688#else
689 if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
690 user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
691 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
692 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
693 } else {
694 std::string data_dir = GetUserDirectory("XDG_DATA_HOME");
695 std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME");
696 std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME");
697
698 user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
699 paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
700 paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
701 }
702#endif
703 paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
704 paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
705 paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
706 paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
707 paths.emplace(UserPath::ScreenshotsDir, user_path + SCREENSHOTS_DIR DIR_SEP);
708 paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
709 paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
710 paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
711 // TODO: Put the logs in a better location for each OS
712 paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
713 }
714
715 if (!new_path.empty()) {
716 if (!IsDirectory(new_path)) {
717 LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path);
718 return paths[path];
719 } else {
720 paths[path] = new_path;
721 }
722
723 switch (path) {
724 case UserPath::RootDir:
725 user_path = paths[UserPath::RootDir] + DIR_SEP;
726 break;
727 case UserPath::UserDir:
728 user_path = paths[UserPath::RootDir] + DIR_SEP;
729 paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP;
730 paths[UserPath::CacheDir] = user_path + CACHE_DIR DIR_SEP;
731 paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP;
732 paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP;
733 break;
734 default:
735 break;
736 }
737 }
738
739 return paths[path];
740}
741
742std::string GetHactoolConfigurationPath() {
743#ifdef _WIN32
744 PWSTR pw_local_path = nullptr;
745 if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK)
746 return "";
747 std::string local_path = Common::UTF16ToUTF8(pw_local_path);
748 CoTaskMemFree(pw_local_path);
749 return local_path + "\\.switch";
750#else
751 return GetHomeDirectory() + "/.switch";
752#endif
753}
754
755std::string GetNANDRegistrationDir(bool system) {
756 if (system)
757 return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
758 return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
759}
760
761std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) {
762 return IOFile(filename, text_file ? "w" : "wb").WriteString(str);
763}
764
765std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) {
766 IOFile file(filename, text_file ? "r" : "rb");
767
768 if (!file.IsOpen())
769 return 0;
770
771 str.resize(static_cast<u32>(file.GetSize()));
772 return file.ReadArray(&str[0], str.size());
773}
774
775void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
776 std::array<char, 4>& extension) {
777 static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, ";
778
779 // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces.
780 short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}};
781 extension = {{' ', ' ', ' ', '\0'}};
782
783 auto point = filename.rfind('.');
784 if (point == filename.size() - 1) {
785 point = filename.rfind('.', point);
786 }
787
788 // Get short name.
789 int j = 0;
790 for (char letter : filename.substr(0, point)) {
791 if (forbidden_characters.find(letter, 0) != std::string::npos) {
792 continue;
793 }
794 if (j == 8) {
795 // TODO(Link Mauve): also do that for filenames containing a space.
796 // TODO(Link Mauve): handle multiple files having the same short name.
797 short_name[6] = '~';
798 short_name[7] = '1';
799 break;
800 }
801 short_name[j++] = static_cast<char>(std::toupper(letter));
802 }
803
804 // Get extension.
805 if (point != std::string::npos) {
806 j = 0;
807 for (char letter : filename.substr(point + 1, 3)) {
808 extension[j++] = static_cast<char>(std::toupper(letter));
809 }
810 }
811}
812
813std::vector<std::string> SplitPathComponents(std::string_view filename) {
814 std::string copy(filename);
815 std::replace(copy.begin(), copy.end(), '\\', '/');
816 std::vector<std::string> out;
817
818 std::stringstream stream(copy);
819 std::string item;
820 while (std::getline(stream, item, '/')) {
821 out.push_back(std::move(item));
822 }
823
824 return out;
825}
826
827std::string_view GetParentPath(std::string_view path) {
828 const auto name_bck_index = path.rfind('\\');
829 const auto name_fwd_index = path.rfind('/');
830 std::size_t name_index;
831
832 if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
833 name_index = std::min(name_bck_index, name_fwd_index);
834 } else {
835 name_index = std::max(name_bck_index, name_fwd_index);
836 }
837
838 return path.substr(0, name_index);
839}
840
841std::string_view GetPathWithoutTop(std::string_view path) {
842 if (path.empty()) {
843 return path;
844 }
845
846 while (path[0] == '\\' || path[0] == '/') {
847 path.remove_prefix(1);
848 if (path.empty()) {
849 return path;
850 }
851 }
852
853 const auto name_bck_index = path.find('\\');
854 const auto name_fwd_index = path.find('/');
855 return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
856}
857
858std::string_view GetFilename(std::string_view path) {
859 const auto name_index = path.find_last_of("\\/");
860
861 if (name_index == std::string_view::npos) {
862 return {};
863 }
864
865 return path.substr(name_index + 1);
866}
867
868std::string_view GetExtensionFromFilename(std::string_view name) {
869 const std::size_t index = name.rfind('.');
870
871 if (index == std::string_view::npos) {
872 return {};
873 }
874
875 return name.substr(index + 1);
876}
877
878std::string_view RemoveTrailingSlash(std::string_view path) {
879 if (path.empty()) {
880 return path;
881 }
882
883 if (path.back() == '\\' || path.back() == '/') {
884 path.remove_suffix(1);
885 return path;
886 }
887
888 return path;
889}
890
891std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
892 std::string path(path_);
893 char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
894 char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
895
896 if (directory_separator == DirectorySeparator::PlatformDefault) {
897#ifdef _WIN32
898 type1 = '/';
899 type2 = '\\';
900#endif
901 }
902
903 std::replace(path.begin(), path.end(), type1, type2);
904
905 auto start = path.begin();
906#ifdef _WIN32
907 // allow network paths which start with a double backslash (e.g. \\server\share)
908 if (start != path.end())
909 ++start;
910#endif
911 path.erase(std::unique(start, path.end(),
912 [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
913 path.end());
914 return std::string(RemoveTrailingSlash(path));
915}
916
917IOFile::IOFile() = default;
918
919IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
920 void(Open(filename, openmode, flags));
921}
922
923IOFile::~IOFile() {
924 Close();
925}
926
927IOFile::IOFile(IOFile&& other) noexcept {
928 Swap(other);
929}
930
931IOFile& IOFile::operator=(IOFile&& other) noexcept {
932 Swap(other);
933 return *this;
934}
935
936void IOFile::Swap(IOFile& other) noexcept {
937 std::swap(m_file, other.m_file);
938}
939
940bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
941 Close();
942 bool m_good;
943#ifdef _WIN32
944 if (flags != 0) {
945 m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
946 Common::UTF8ToUTF16W(openmode).c_str(), flags);
947 m_good = m_file != nullptr;
948 } else {
949 m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
950 Common::UTF8ToUTF16W(openmode).c_str()) == 0;
951 }
952#else
953 m_file = std::fopen(filename.c_str(), openmode);
954 m_good = m_file != nullptr;
955#endif
956
957 return m_good;
958}
959
960bool IOFile::Close() {
961 if (!IsOpen() || 0 != std::fclose(m_file)) {
962 return false;
963 }
964
965 m_file = nullptr;
966 return true;
967}
968
969u64 IOFile::GetSize() const {
970 if (IsOpen()) {
971 return FS::GetSize(m_file);
972 }
973 return 0;
974}
975
976bool IOFile::Seek(s64 off, int origin) const {
977 return IsOpen() && 0 == fseeko(m_file, off, origin);
978}
979
980u64 IOFile::Tell() const {
981 if (IsOpen()) {
982 return ftello(m_file);
983 }
984 return std::numeric_limits<u64>::max();
985}
986
987bool IOFile::Flush() {
988 return IsOpen() && 0 == std::fflush(m_file);
989}
990
991std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) const {
992 if (!IsOpen()) {
993 return std::numeric_limits<std::size_t>::max();
994 }
995
996 if (length == 0) {
997 return 0;
998 }
999
1000 DEBUG_ASSERT(data != nullptr);
1001
1002 return std::fread(data, data_size, length, m_file);
1003}
1004
1005std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
1006 if (!IsOpen()) {
1007 return std::numeric_limits<std::size_t>::max();
1008 }
1009
1010 if (length == 0) {
1011 return 0;
1012 }
1013
1014 DEBUG_ASSERT(data != nullptr);
1015
1016 return std::fwrite(data, data_size, length, m_file);
1017}
1018
1019bool IOFile::Resize(u64 size) {
1020 return IsOpen() && 0 ==
1021#ifdef _WIN32
1022 // ector: _chsize sucks, not 64-bit safe
1023 // F|RES: changed to _chsize_s. i think it is 64-bit safe
1024 _chsize_s(_fileno(m_file), size)
1025#else
1026 // TODO: handle 64bit and growing
1027 ftruncate(fileno(m_file), size)
1028#endif
1029 ;
1030}
1031
1032} // namespace Common::FS
diff --git a/src/common/file_util.h b/src/common/file_util.h
deleted file mode 100644
index 840cde2a6..000000000
--- a/src/common/file_util.h
+++ /dev/null
@@ -1,298 +0,0 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra 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 <array>
8#include <cstdio>
9#include <fstream>
10#include <functional>
11#include <limits>
12#include <optional>
13#include <string>
14#include <string_view>
15#include <type_traits>
16#include <vector>
17#include "common/common_types.h"
18#ifdef _MSC_VER
19#include "common/string_util.h"
20#endif
21
22namespace Common::FS {
23
24// User paths for GetUserPath
25enum class UserPath {
26 CacheDir,
27 ConfigDir,
28 KeysDir,
29 LogDir,
30 NANDDir,
31 RootDir,
32 SDMCDir,
33 LoadDir,
34 DumpDir,
35 ScreenshotsDir,
36 ShaderDir,
37 SysDataDir,
38 UserDir,
39};
40
41// FileSystem tree node/
42struct FSTEntry {
43 bool isDirectory;
44 u64 size; // file length or number of entries from children
45 std::string physicalName; // name on disk
46 std::string virtualName; // name in FST names table
47 std::vector<FSTEntry> children;
48};
49
50// Returns true if file filename exists
51[[nodiscard]] bool Exists(const std::string& filename);
52
53// Returns true if filename is a directory
54[[nodiscard]] bool IsDirectory(const std::string& filename);
55
56// Returns the size of filename (64bit)
57[[nodiscard]] u64 GetSize(const std::string& filename);
58
59// Overloaded GetSize, accepts file descriptor
60[[nodiscard]] u64 GetSize(int fd);
61
62// Overloaded GetSize, accepts FILE*
63[[nodiscard]] u64 GetSize(FILE* f);
64
65// Returns true if successful, or path already exists.
66bool CreateDir(const std::string& filename);
67
68// Creates the full path of fullPath returns true on success
69bool CreateFullPath(const std::string& fullPath);
70
71// Deletes a given filename, return true on success
72// Doesn't supports deleting a directory
73bool Delete(const std::string& filename);
74
75// Deletes a directory filename, returns true on success
76bool DeleteDir(const std::string& filename);
77
78// renames file srcFilename to destFilename, returns true on success
79bool Rename(const std::string& srcFilename, const std::string& destFilename);
80
81// copies file srcFilename to destFilename, returns true on success
82bool Copy(const std::string& srcFilename, const std::string& destFilename);
83
84// creates an empty file filename, returns true on success
85bool CreateEmptyFile(const std::string& filename);
86
87/**
88 * @param num_entries_out to be assigned by the callable with the number of iterated directory
89 * entries, never null
90 * @param directory the path to the enclosing directory
91 * @param virtual_name the entry name, without any preceding directory info
92 * @return whether handling the entry succeeded
93 */
94using DirectoryEntryCallable = std::function<bool(
95 u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
96
97/**
98 * Scans a directory, calling the callback for each file/directory contained within.
99 * If the callback returns failure, scanning halts and this function returns failure as well
100 * @param num_entries_out assigned by the function with the number of iterated directory entries,
101 * can be null
102 * @param directory the directory to scan
103 * @param callback The callback which will be called for each entry
104 * @return whether scanning the directory succeeded
105 */
106bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
107 DirectoryEntryCallable callback);
108
109/**
110 * Scans the directory tree, storing the results.
111 * @param directory the parent directory to start scanning from
112 * @param parent_entry FSTEntry where the filesystem tree results will be stored.
113 * @param recursion Number of children directories to read before giving up.
114 * @return the total number of files/directories found
115 */
116u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
117 unsigned int recursion = 0);
118
119// deletes the given directory and anything under it. Returns true on success.
120bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
121
122// Returns the current directory
123[[nodiscard]] std::optional<std::string> GetCurrentDir();
124
125// Create directory and copy contents (does not overwrite existing files)
126void CopyDir(const std::string& source_path, const std::string& dest_path);
127
128// Set the current directory to given directory
129bool SetCurrentDir(const std::string& directory);
130
131// Returns a pointer to a string with a yuzu data dir in the user's home
132// directory. To be used in "multi-user" mode (that is, installed).
133const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
134
135[[nodiscard]] std::string GetHactoolConfigurationPath();
136
137[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false);
138
139// Returns the path to where the sys file are
140[[nodiscard]] std::string GetSysDirectory();
141
142#ifdef __APPLE__
143[[nodiscard]] std::string GetBundleDirectory();
144#endif
145
146#ifdef _WIN32
147[[nodiscard]] const std::string& GetExeDirectory();
148[[nodiscard]] std::string AppDataRoamingDirectory();
149#endif
150
151std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
152
153std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
154
155/**
156 * Splits the filename into 8.3 format
157 * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
158 * @param filename The normal filename to use
159 * @param short_name A 9-char array in which the short name will be written
160 * @param extension A 4-char array in which the extension will be written
161 */
162void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
163 std::array<char, 4>& extension);
164
165// Splits the path on '/' or '\' and put the components into a vector
166// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
167[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename);
168
169// Gets all of the text up to the last '/' or '\' in the path.
170[[nodiscard]] std::string_view GetParentPath(std::string_view path);
171
172// Gets all of the text after the first '/' or '\' in the path.
173[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
174
175// Gets the filename of the path
176[[nodiscard]] std::string_view GetFilename(std::string_view path);
177
178// Gets the extension of the filename
179[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
180
181// Removes the final '/' or '\' if one exists
182[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
183
184// Creates a new vector containing indices [first, last) from the original.
185template <typename T>
186[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first,
187 std::size_t last) {
188 if (first >= last) {
189 return {};
190 }
191 last = std::min<std::size_t>(last, vector.size());
192 return std::vector<T>(vector.begin() + first, vector.begin() + first + last);
193}
194
195enum class DirectorySeparator {
196 ForwardSlash,
197 BackwardSlash,
198 PlatformDefault,
199};
200
201// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
202// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
203[[nodiscard]] std::string SanitizePath(
204 std::string_view path,
205 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
206
207// To deal with Windows being dumb at Unicode
208template <typename T>
209void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
210#ifdef _MSC_VER
211 fstream.open(Common::UTF8ToUTF16W(filename), openmode);
212#else
213 fstream.open(filename, openmode);
214#endif
215}
216
217// simple wrapper for cstdlib file functions to
218// hopefully will make error checking easier
219// and make forgetting an fclose() harder
220class IOFile : public NonCopyable {
221public:
222 IOFile();
223 // flags is used for windows specific file open mode flags, which
224 // allows yuzu to open the logs in shared write mode, so that the file
225 // isn't considered "locked" while yuzu is open and people can open the log file and view it
226 IOFile(const std::string& filename, const char openmode[], int flags = 0);
227
228 ~IOFile();
229
230 IOFile(IOFile&& other) noexcept;
231 IOFile& operator=(IOFile&& other) noexcept;
232
233 void Swap(IOFile& other) noexcept;
234
235 bool Open(const std::string& filename, const char openmode[], int flags = 0);
236 bool Close();
237
238 template <typename T>
239 std::size_t ReadArray(T* data, std::size_t length) const {
240 static_assert(std::is_trivially_copyable_v<T>,
241 "Given array does not consist of trivially copyable objects");
242
243 return ReadImpl(data, length, sizeof(T));
244 }
245
246 template <typename T>
247 std::size_t WriteArray(const T* data, std::size_t length) {
248 static_assert(std::is_trivially_copyable_v<T>,
249 "Given array does not consist of trivially copyable objects");
250
251 return WriteImpl(data, length, sizeof(T));
252 }
253
254 template <typename T>
255 std::size_t ReadBytes(T* data, std::size_t length) const {
256 static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
257 return ReadArray(reinterpret_cast<char*>(data), length);
258 }
259
260 template <typename T>
261 std::size_t WriteBytes(const T* data, std::size_t length) {
262 static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
263 return WriteArray(reinterpret_cast<const char*>(data), length);
264 }
265
266 template <typename T>
267 std::size_t WriteObject(const T& object) {
268 static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer");
269 return WriteArray(&object, 1);
270 }
271
272 std::size_t WriteString(std::string_view str) {
273 return WriteArray(str.data(), str.length());
274 }
275
276 [[nodiscard]] bool IsOpen() const {
277 return nullptr != m_file;
278 }
279
280 bool Seek(s64 off, int origin) const;
281 [[nodiscard]] u64 Tell() const;
282 [[nodiscard]] u64 GetSize() const;
283 bool Resize(u64 size);
284 bool Flush();
285
286 // clear error state
287 void Clear() {
288 std::clearerr(m_file);
289 }
290
291private:
292 std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const;
293 std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
294
295 std::FILE* m_file = nullptr;
296};
297
298} // namespace Common::FS
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..209f9664b
--- /dev/null
+++ b/src/common/fs/file.h
@@ -0,0 +1,450 @@
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 : NonCopyable {
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 virtual ~IOFile();
146
147 IOFile(IOFile&& other) noexcept;
148 IOFile& operator=(IOFile&& other) noexcept;
149
150 /**
151 * Gets the path of the file.
152 *
153 * @returns The path of the file.
154 */
155 [[nodiscard]] std::filesystem::path GetPath() const;
156
157 /**
158 * Gets the access mode of the file.
159 *
160 * @returns The access mode of the file.
161 */
162 [[nodiscard]] FileAccessMode GetAccessMode() const;
163
164 /**
165 * Gets the type of the file.
166 *
167 * @returns The type of the file.
168 */
169 [[nodiscard]] FileType GetType() const;
170
171 /**
172 * Opens a file at path with the specified file access mode.
173 * This function behaves differently depending on the FileAccessMode.
174 * These behaviors are documented in each enum value of FileAccessMode.
175 *
176 * @param path Filesystem path
177 * @param mode File access mode
178 * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
179 * @param flag (Windows only) File-share access flag, default is ShareReadOnly
180 */
181 void Open(const std::filesystem::path& path, FileAccessMode mode,
182 FileType type = FileType::BinaryFile,
183 FileShareFlag flag = FileShareFlag::ShareReadOnly);
184
185#ifdef _WIN32
186 template <typename Path>
187 [[nodiscard]] void Open(const Path& path, FileAccessMode mode,
188 FileType type = FileType::BinaryFile,
189 FileShareFlag flag = FileShareFlag::ShareReadOnly) {
190 using ValueType = typename Path::value_type;
191 if constexpr (IsChar<ValueType>) {
192 Open(ToU8String(path), mode, type, flag);
193 } else {
194 Open(std::filesystem::path{path}, mode, type, flag);
195 }
196 }
197#endif
198
199 /// Closes the file if it is opened.
200 void Close();
201
202 /**
203 * Checks whether the file is open.
204 * Use this to check whether the calls to Open() or Close() succeeded.
205 *
206 * @returns True if the file is open, false otherwise.
207 */
208 [[nodiscard]] bool IsOpen() const;
209
210 /**
211 * Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
212 * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
213 * ReadObject and T must be a trivially copyable object.
214 *
215 * See ReadSpan for more details if T is a contiguous container.
216 * See ReadObject for more details if T is a trivially copyable object.
217 *
218 * @tparam T Contiguous container or trivially copyable object
219 *
220 * @param data Container of T::value_type data or reference to object
221 *
222 * @returns Count of T::value_type data or objects successfully read.
223 */
224 template <typename T>
225 [[nodiscard]] size_t Read(T& data) const {
226 if constexpr (IsSTLContainer<T>) {
227 using ContiguousType = typename T::value_type;
228 static_assert(std::is_trivially_copyable_v<ContiguousType>,
229 "Data type must be trivially copyable.");
230 return ReadSpan<ContiguousType>(data);
231 } else {
232 return ReadObject(data) ? 1 : 0;
233 }
234 }
235
236 /**
237 * Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
238 * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
239 * WriteObject and T must be a trivially copyable object.
240 *
241 * See WriteSpan for more details if T is a contiguous container.
242 * See WriteObject for more details if T is a trivially copyable object.
243 *
244 * @tparam T Contiguous container or trivially copyable object
245 *
246 * @param data Container of T::value_type data or const reference to object
247 *
248 * @returns Count of T::value_type data or objects successfully written.
249 */
250 template <typename T>
251 [[nodiscard]] size_t Write(const T& data) const {
252 if constexpr (IsSTLContainer<T>) {
253 using ContiguousType = typename T::value_type;
254 static_assert(std::is_trivially_copyable_v<ContiguousType>,
255 "Data type must be trivially copyable.");
256 return WriteSpan<ContiguousType>(data);
257 } else {
258 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
259 return WriteObject(data) ? 1 : 0;
260 }
261 }
262
263 /**
264 * Reads a span of T data from a file sequentially.
265 * This function reads from the current position of the file pointer and
266 * advances it by the (count of T * sizeof(T)) bytes successfully read.
267 *
268 * Failures occur when:
269 * - The file is not open
270 * - The opened file lacks read permissions
271 * - Attempting to read beyond the end-of-file
272 *
273 * @tparam T Data type
274 *
275 * @param data Span of T data
276 *
277 * @returns Count of T data successfully read.
278 */
279 template <typename T>
280 [[nodiscard]] size_t ReadSpan(std::span<T> data) const {
281 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
282
283 if (!IsOpen()) {
284 return 0;
285 }
286
287 return std::fread(data.data(), sizeof(T), data.size(), file);
288 }
289
290 /**
291 * Writes a span of T data to a file sequentially.
292 * This function writes from the current position of the file pointer and
293 * advances it by the (count of T * sizeof(T)) bytes successfully written.
294 *
295 * Failures occur when:
296 * - The file is not open
297 * - The opened file lacks write permissions
298 *
299 * @tparam T Data type
300 *
301 * @param data Span of T data
302 *
303 * @returns Count of T data successfully written.
304 */
305 template <typename T>
306 [[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
307 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
308
309 if (!IsOpen()) {
310 return 0;
311 }
312
313 return std::fwrite(data.data(), sizeof(T), data.size(), file);
314 }
315
316 /**
317 * Reads a T object from a file sequentially.
318 * This function reads from the current position of the file pointer and
319 * advances it by the sizeof(T) bytes successfully read.
320 *
321 * Failures occur when:
322 * - The file is not open
323 * - The opened file lacks read permissions
324 * - Attempting to read beyond the end-of-file
325 *
326 * @tparam T Data type
327 *
328 * @param object Reference to object
329 *
330 * @returns True if the object is successfully read from the file, false otherwise.
331 */
332 template <typename T>
333 [[nodiscard]] bool ReadObject(T& object) const {
334 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
335 static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
336
337 if (!IsOpen()) {
338 return false;
339 }
340
341 return std::fread(&object, sizeof(T), 1, file) == 1;
342 }
343
344 /**
345 * Writes a T object to a file sequentially.
346 * This function writes from the current position of the file pointer and
347 * advances it by the sizeof(T) bytes successfully written.
348 *
349 * Failures occur when:
350 * - The file is not open
351 * - The opened file lacks write permissions
352 *
353 * @tparam T Data type
354 *
355 * @param object Const reference to object
356 *
357 * @returns True if the object is successfully written to the file, false otherwise.
358 */
359 template <typename T>
360 [[nodiscard]] bool WriteObject(const T& object) const {
361 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
362 static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
363
364 if (!IsOpen()) {
365 return false;
366 }
367
368 return std::fwrite(&object, sizeof(T), 1, file) == 1;
369 }
370
371 /**
372 * Specialized function to read a string of a given length from a file sequentially.
373 * This function writes from the current position of the file pointer and
374 * advances it by the number of characters successfully read.
375 * The size of the returned string may not match length if not all bytes are successfully read.
376 *
377 * @param length Length of the string
378 *
379 * @returns A string read from the file.
380 */
381 [[nodiscard]] std::string ReadString(size_t length) const;
382
383 /**
384 * Specialized function to write a string to a file sequentially.
385 * This function writes from the current position of the file pointer and
386 * advances it by the number of characters successfully written.
387 *
388 * @param string Span of const char backed std::string or std::string_view
389 *
390 * @returns Number of characters successfully written.
391 */
392 [[nodiscard]] size_t WriteString(std::span<const char> string) const;
393
394 /**
395 * Flushes any unwritten buffered data into the file.
396 *
397 * @returns True if the flush was successful, false otherwise.
398 */
399 [[nodiscard]] bool Flush() const;
400
401 /**
402 * Resizes the file to a given size.
403 * If the file is resized to a smaller size, the remainder of the file is discarded.
404 * If the file is resized to a larger size, the new area appears as if zero-filled.
405 *
406 * Failures occur when:
407 * - The file is not open
408 *
409 * @param size File size in bytes
410 *
411 * @returns True if the file resize succeeded, false otherwise.
412 */
413 [[nodiscard]] bool SetSize(u64 size) const;
414
415 /**
416 * Gets the size of the file.
417 *
418 * Failures occur when:
419 * - The file is not open
420 *
421 * @returns The file size in bytes of the file. Returns 0 on failure.
422 */
423 [[nodiscard]] u64 GetSize() const;
424
425 /**
426 * Moves the current position of the file pointer with the specified offset and seek origin.
427 *
428 * @param offset Offset from seek origin
429 * @param origin Seek origin
430 *
431 * @returns True if the file pointer has moved to the specified offset, false otherwise.
432 */
433 [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
434
435 /**
436 * Gets the current position of the file pointer.
437 *
438 * @returns The current position of the file pointer.
439 */
440 [[nodiscard]] s64 Tell() const;
441
442private:
443 std::filesystem::path file_path;
444 FileAccessMode file_access_mode;
445 FileType file_type;
446
447 std::FILE* file = nullptr;
448};
449
450} // 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
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 96efa977d..6aa8ac960 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -11,13 +11,13 @@
11#include <mutex> 11#include <mutex>
12#include <thread> 12#include <thread>
13#include <vector> 13#include <vector>
14
14#ifdef _WIN32 15#ifdef _WIN32
15#include <share.h> // For _SH_DENYWR
16#include <windows.h> // For OutputDebugStringW 16#include <windows.h> // For OutputDebugStringW
17#else
18#define _SH_DENYWR 0
19#endif 17#endif
18
20#include "common/assert.h" 19#include "common/assert.h"
20#include "common/fs/fs.h"
21#include "common/logging/backend.h" 21#include "common/logging/backend.h"
22#include "common/logging/log.h" 22#include "common/logging/log.h"
23#include "common/logging/text_formatter.h" 23#include "common/logging/text_formatter.h"
@@ -148,19 +148,16 @@ void ColorConsoleBackend::Write(const Entry& entry) {
148 PrintColoredMessage(entry); 148 PrintColoredMessage(entry);
149} 149}
150 150
151FileBackend::FileBackend(const std::string& filename) { 151FileBackend::FileBackend(const std::filesystem::path& filename) {
152 const auto old_filename = filename + ".old.txt"; 152 auto old_filename = filename;
153 old_filename += ".old.txt";
153 154
154 if (FS::Exists(old_filename)) { 155 // Existence checks are done within the functions themselves.
155 FS::Delete(old_filename); 156 // We don't particularly care if these succeed or not.
156 } 157 void(FS::RemoveFile(old_filename));
157 if (FS::Exists(filename)) { 158 void(FS::RenameFile(filename, old_filename));
158 FS::Rename(filename, old_filename);
159 }
160 159
161 // _SH_DENYWR allows read only access to the file for other programs. 160 file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
162 // It is #defined to 0 on other platforms
163 file = FS::IOFile(filename, "w", _SH_DENYWR);
164} 161}
165 162
166void FileBackend::Write(const Entry& entry) { 163void FileBackend::Write(const Entry& entry) {
@@ -181,7 +178,7 @@ void FileBackend::Write(const Entry& entry) {
181 178
182 bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); 179 bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
183 if (entry.log_level >= Level::Error) { 180 if (entry.log_level >= Level::Error) {
184 file.Flush(); 181 void(file.Flush());
185 } 182 }
186} 183}
187 184
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 9dd2589c3..eb629a33f 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -4,10 +4,11 @@
4#pragma once 4#pragma once
5 5
6#include <chrono> 6#include <chrono>
7#include <filesystem>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9#include <string_view> 10#include <string_view>
10#include "common/file_util.h" 11#include "common/fs/file.h"
11#include "common/logging/filter.h" 12#include "common/logging/filter.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13 14
@@ -81,7 +82,7 @@ public:
81 */ 82 */
82class FileBackend : public Backend { 83class FileBackend : public Backend {
83public: 84public:
84 explicit FileBackend(const std::string& filename); 85 explicit FileBackend(const std::filesystem::path& filename);
85 86
86 static const char* Name() { 87 static const char* Name() {
87 return "file"; 88 return "file";
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
index d537517db..d1afd1f1d 100644
--- a/src/common/nvidia_flags.cpp
+++ b/src/common/nvidia_flags.cpp
@@ -2,24 +2,30 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <filesystem> 5#include <cstdlib>
6#include <stdlib.h>
7 6
8#include <fmt/format.h> 7#include <fmt/format.h>
9 8
10#include "common/file_util.h" 9#include "common/fs/file.h"
10#include "common/fs/fs.h"
11#include "common/fs/path_util.h"
11#include "common/nvidia_flags.h" 12#include "common/nvidia_flags.h"
12 13
13namespace Common { 14namespace Common {
14 15
15void ConfigureNvidiaEnvironmentFlags() { 16void ConfigureNvidiaEnvironmentFlags() {
16#ifdef _WIN32 17#ifdef _WIN32
17 const std::string shader_path = Common::FS::SanitizePath( 18 const auto nvidia_shader_dir =
18 fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir))); 19 Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "nvidia";
19 const std::string windows_path = 20
20 Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash); 21 if (!Common::FS::CreateDirs(nvidia_shader_dir)) {
21 void(Common::FS::CreateFullPath(shader_path + '/')); 22 return;
22 void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str())); 23 }
24
25 const auto windows_path_string =
26 Common::FS::PathToUTF8String(nvidia_shader_dir.lexically_normal());
27
28 void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str()));
23 void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); 29 void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
24#endif 30#endif
25} 31}
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index e29cbf506..bcb4e4be1 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -5,7 +5,7 @@
5#include <string_view> 5#include <string_view>
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/file_util.h" 8#include "common/fs/path_util.h"
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/settings.h" 10#include "common/settings.h"
11 11
@@ -34,6 +34,10 @@ void LogSettings() {
34 LOG_INFO(Config, "{}: {}", name, value); 34 LOG_INFO(Config, "{}: {}", name, value);
35 }; 35 };
36 36
37 const auto log_path = [](std::string_view name, const std::filesystem::path& path) {
38 LOG_INFO(Config, "{}: {}", name, Common::FS::PathToUTF8String(path));
39 };
40
37 LOG_INFO(Config, "yuzu Configuration:"); 41 LOG_INFO(Config, "yuzu Configuration:");
38 log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); 42 log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
39 log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); 43 log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
@@ -59,11 +63,11 @@ void LogSettings() {
59 log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); 63 log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
60 log_setting("Audio_OutputDevice", values.audio_device_id); 64 log_setting("Audio_OutputDevice", values.audio_device_id);
61 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); 65 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
62 log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)); 66 log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
63 log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); 67 log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
64 log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)); 68 log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir));
65 log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)); 69 log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
66 log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)); 70 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
67 log_setting("Debugging_ProgramArgs", values.program_args); 71 log_setting("Debugging_ProgramArgs", values.program_args);
68 log_setting("Services_BCATBackend", values.bcat_backend); 72 log_setting("Services_BCATBackend", values.bcat_backend);
69 log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local); 73 log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local);
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 7b614ad89..e6344fd41 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -9,7 +9,6 @@
9#include <locale> 9#include <locale>
10#include <sstream> 10#include <sstream>
11 11
12#include "common/common_paths.h"
13#include "common/logging/log.h" 12#include "common/logging/log.h"
14#include "common/string_util.h" 13#include "common/string_util.h"
15 14
@@ -93,18 +92,6 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
93 return true; 92 return true;
94} 93}
95 94
96void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
97 const std::string& _Filename) {
98 _CompleteFilename = _Path;
99
100 // check for seperator
101 if (DIR_SEP_CHR != *_CompleteFilename.rbegin())
102 _CompleteFilename += DIR_SEP_CHR;
103
104 // add the filename
105 _CompleteFilename += _Filename;
106}
107
108void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) { 95void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) {
109 std::istringstream iss(str); 96 std::istringstream iss(str);
110 output.resize(1); 97 output.resize(1);
diff --git a/src/common/string_util.h b/src/common/string_util.h
index a32c07c06..7e90a9ca5 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -32,8 +32,6 @@ void SplitString(const std::string& str, char delim, std::vector<std::string>& o
32bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, 32bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename,
33 std::string* _pExtension); 33 std::string* _pExtension);
34 34
35void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path,
36 const std::string& _Filename);
37[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, 35[[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src,
38 const std::string& dest); 36 const std::string& dest);
39 37
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 826a00ad6..c5004b7b4 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -6,7 +6,7 @@
6#include <memory> 6#include <memory>
7#include <utility> 7#include <utility>
8 8
9#include "common/file_util.h" 9#include "common/fs/fs.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/microprofile.h" 11#include "common/microprofile.h"
12#include "common/settings.h" 12#include "common/settings.h"
@@ -121,7 +121,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
121 dir->GetName()); 121 dir->GetName());
122 } 122 }
123 123
124 if (Common::FS::IsDirectory(path)) { 124 if (Common::FS::IsDir(path)) {
125 return vfs->OpenFile(path + "/main", FileSys::Mode::Read); 125 return vfs->OpenFile(path + "/main", FileSys::Mode::Read);
126 } 126 }
127 127
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index a4b739c63..fb451a423 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -18,8 +18,9 @@
18#include <mbedtls/cmac.h> 18#include <mbedtls/cmac.h>
19#include <mbedtls/sha256.h> 19#include <mbedtls/sha256.h>
20#include "common/common_funcs.h" 20#include "common/common_funcs.h"
21#include "common/common_paths.h" 21#include "common/fs/file.h"
22#include "common/file_util.h" 22#include "common/fs/fs.h"
23#include "common/fs/path_util.h"
23#include "common/hex_util.h" 24#include "common/hex_util.h"
24#include "common/logging/log.h" 25#include "common/logging/log.h"
25#include "common/settings.h" 26#include "common/settings.h"
@@ -325,46 +326,55 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
325} 326}
326 327
327std::optional<Key128> DeriveSDSeed() { 328std::optional<Key128> DeriveSDSeed() {
328 const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 329 const auto system_save_43_path =
329 "/system/save/8000000000000043", 330 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000043";
330 "rb+"); 331 const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read,
332 Common::FS::FileType::BinaryFile};
333
331 if (!save_43.IsOpen()) { 334 if (!save_43.IsOpen()) {
332 return std::nullopt; 335 return std::nullopt;
333 } 336 }
334 337
335 const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + 338 const auto sd_private_path =
336 "/Nintendo/Contents/private", 339 Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/private";
337 "rb+"); 340
341 const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read,
342 Common::FS::FileType::BinaryFile};
343
338 if (!sd_private.IsOpen()) { 344 if (!sd_private.IsOpen()) {
339 return std::nullopt; 345 return std::nullopt;
340 } 346 }
341 347
342 std::array<u8, 0x10> private_seed{}; 348 std::array<u8, 0x10> private_seed{};
343 if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { 349 if (sd_private.Read(private_seed) != private_seed.size()) {
344 return std::nullopt; 350 return std::nullopt;
345 } 351 }
346 352
347 std::array<u8, 0x10> buffer{}; 353 std::array<u8, 0x10> buffer{};
348 std::size_t offset = 0; 354 s64 offset = 0;
349 for (; offset + 0x10 < save_43.GetSize(); ++offset) { 355 for (; offset + 0x10 < static_cast<s64>(save_43.GetSize()); ++offset) {
350 if (!save_43.Seek(offset, SEEK_SET)) { 356 if (!save_43.Seek(offset)) {
357 return std::nullopt;
358 }
359
360 if (save_43.Read(buffer) != buffer.size()) {
351 return std::nullopt; 361 return std::nullopt;
352 } 362 }
353 363
354 save_43.ReadBytes(buffer.data(), buffer.size());
355 if (buffer == private_seed) { 364 if (buffer == private_seed) {
356 break; 365 break;
357 } 366 }
358 } 367 }
359 368
360 if (!save_43.Seek(offset + 0x10, SEEK_SET)) { 369 if (!save_43.Seek(offset + 0x10)) {
361 return std::nullopt; 370 return std::nullopt;
362 } 371 }
363 372
364 Key128 seed{}; 373 Key128 seed{};
365 if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { 374 if (save_43.Read(seed) != seed.size()) {
366 return std::nullopt; 375 return std::nullopt;
367 } 376 }
377
368 return seed; 378 return seed;
369} 379}
370 380
@@ -435,7 +445,7 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
435 } 445 }
436 446
437 std::vector<u8> buffer(ticket_save.GetSize()); 447 std::vector<u8> buffer(ticket_save.GetSize());
438 if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { 448 if (ticket_save.Read(buffer) != buffer.size()) {
439 return {}; 449 return {};
440 } 450 }
441 451
@@ -566,27 +576,26 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
566 576
567KeyManager::KeyManager() { 577KeyManager::KeyManager() {
568 // Initialize keys 578 // Initialize keys
569 const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); 579 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
570 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
571 580
572 if (!Common::FS::Exists(yuzu_keys_dir)) { 581 if (!Common::FS::CreateDir(yuzu_keys_dir)) {
573 Common::FS::CreateDir(yuzu_keys_dir); 582 LOG_ERROR(Core, "Failed to create the keys directory.");
574 } 583 }
575 584
576 if (Settings::values.use_dev_keys) { 585 if (Settings::values.use_dev_keys) {
577 dev_mode = true; 586 dev_mode = true;
578 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); 587 LoadFromFile(yuzu_keys_dir / "dev.keys", false);
579 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); 588 LoadFromFile(yuzu_keys_dir / "dev.keys_autogenerated", false);
580 } else { 589 } else {
581 dev_mode = false; 590 dev_mode = false;
582 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); 591 LoadFromFile(yuzu_keys_dir / "prod.keys", false);
583 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); 592 LoadFromFile(yuzu_keys_dir / "prod.keys_autogenerated", false);
584 } 593 }
585 594
586 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); 595 LoadFromFile(yuzu_keys_dir / "title.keys", true);
587 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); 596 LoadFromFile(yuzu_keys_dir / "title.keys_autogenerated", true);
588 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); 597 LoadFromFile(yuzu_keys_dir / "console.keys", false);
589 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); 598 LoadFromFile(yuzu_keys_dir / "console.keys_autogenerated", false);
590} 599}
591 600
592static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { 601static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
@@ -597,9 +606,14 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
597 [](u8 c) { return std::isxdigit(c); }); 606 [](u8 c) { return std::isxdigit(c); });
598} 607}
599 608
600void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { 609void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) {
610 if (!Common::FS::Exists(file_path)) {
611 return;
612 }
613
601 std::ifstream file; 614 std::ifstream file;
602 Common::FS::OpenFStream(file, filename, std::ios_base::in); 615 Common::FS::OpenFileStream(file, file_path, std::ios_base::in);
616
603 if (!file.is_open()) { 617 if (!file.is_open()) {
604 return; 618 return;
605 } 619 }
@@ -694,15 +708,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
694 } 708 }
695} 709}
696 710
697void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
698 const std::string& filename, bool title) {
699 if (Common::FS::Exists(dir1 + DIR_SEP + filename)) {
700 LoadFromFile(dir1 + DIR_SEP + filename, title);
701 } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) {
702 LoadFromFile(dir2 + DIR_SEP + filename, title);
703 }
704}
705
706bool KeyManager::BaseDeriveNecessary() const { 711bool KeyManager::BaseDeriveNecessary() const {
707 const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { 712 const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
708 return !HasKey(key_type, index1, index2); 713 return !HasKey(key_type, index1, index2);
@@ -766,30 +771,35 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
766template <size_t Size> 771template <size_t Size>
767void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, 772void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
768 const std::array<u8, Size>& key) { 773 const std::array<u8, Size>& key) {
769 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); 774 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
775
770 std::string filename = "title.keys_autogenerated"; 776 std::string filename = "title.keys_autogenerated";
777
771 if (category == KeyCategory::Standard) { 778 if (category == KeyCategory::Standard) {
772 filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; 779 filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
773 } else if (category == KeyCategory::Console) { 780 } else if (category == KeyCategory::Console) {
774 filename = "console.keys_autogenerated"; 781 filename = "console.keys_autogenerated";
775 } 782 }
776 783
777 const auto path = yuzu_keys_dir + DIR_SEP + filename; 784 const auto path = yuzu_keys_dir / filename;
778 const auto add_info_text = !Common::FS::Exists(path); 785 const auto add_info_text = !Common::FS::Exists(path);
779 Common::FS::CreateFullPath(path); 786
780 Common::FS::IOFile file{path, "a"}; 787 Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append,
788 Common::FS::FileType::TextFile};
789
781 if (!file.IsOpen()) { 790 if (!file.IsOpen()) {
782 return; 791 return;
783 } 792 }
793
784 if (add_info_text) { 794 if (add_info_text) {
785 file.WriteString( 795 void(file.WriteString(
786 "# This file is autogenerated by Yuzu\n" 796 "# This file is autogenerated by Yuzu\n"
787 "# It serves to store keys that were automatically generated from the normal keys\n" 797 "# It serves to store keys that were automatically generated from the normal keys\n"
788 "# If you are experiencing issues involving keys, it may help to delete this file\n"); 798 "# If you are experiencing issues involving keys, it may help to delete this file\n"));
789 } 799 }
790 800
791 file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))); 801 void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))));
792 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); 802 LoadFromFile(path, category == KeyCategory::Title);
793} 803}
794 804
795void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { 805void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -861,20 +871,17 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
861} 871}
862 872
863bool KeyManager::KeyFileExists(bool title) { 873bool KeyManager::KeyFileExists(bool title) {
864 const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); 874 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
865 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); 875
866 if (title) { 876 if (title) {
867 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || 877 return Common::FS::Exists(yuzu_keys_dir / "title.keys");
868 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
869 } 878 }
870 879
871 if (Settings::values.use_dev_keys) { 880 if (Settings::values.use_dev_keys) {
872 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || 881 return Common::FS::Exists(yuzu_keys_dir / "dev.keys");
873 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
874 } 882 }
875 883
876 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || 884 return Common::FS::Exists(yuzu_keys_dir / "prod.keys");
877 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
878} 885}
879 886
880void KeyManager::DeriveSDSeedLazy() { 887void KeyManager::DeriveSDSeedLazy() {
@@ -1115,15 +1122,21 @@ void KeyManager::PopulateTickets() {
1115 return; 1122 return;
1116 } 1123 }
1117 1124
1118 const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 1125 const auto system_save_e1_path =
1119 "/system/save/80000000000000e1", 1126 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1";
1120 "rb+"); 1127
1121 const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 1128 const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read,
1122 "/system/save/80000000000000e2", 1129 Common::FS::FileType::BinaryFile};
1123 "rb+"); 1130
1131 const auto system_save_e2_path =
1132 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2";
1133
1134 const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read,
1135 Common::FS::FileType::BinaryFile};
1136
1137 const auto blob2 = GetTicketblob(save_e2);
1138 auto res = GetTicketblob(save_e1);
1124 1139
1125 const auto blob2 = GetTicketblob(save2);
1126 auto res = GetTicketblob(save1);
1127 const auto idx = res.size(); 1140 const auto idx = res.size();
1128 res.insert(res.end(), blob2.begin(), blob2.end()); 1141 res.insert(res.end(), blob2.begin(), blob2.end());
1129 1142
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 0a7220286..e771625e1 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <filesystem>
8#include <map> 9#include <map>
9#include <optional> 10#include <optional>
10#include <string> 11#include <string>
@@ -283,9 +284,8 @@ private:
283 std::array<u8, 576> eticket_extended_kek{}; 284 std::array<u8, 576> eticket_extended_kek{};
284 285
285 bool dev_mode; 286 bool dev_mode;
286 void LoadFromFile(const std::string& filename, bool is_title_keys); 287 void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys);
287 void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, 288
288 const std::string& filename, bool title);
289 template <size_t Size> 289 template <size_t Size>
290 void WriteKeyToFile(KeyCategory category, std::string_view keyname, 290 void WriteKeyToFile(KeyCategory category, std::string_view keyname,
291 const std::array<u8, Size>& key); 291 const std::array<u8, Size>& key);
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 7c6304ff0..f3891acf1 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -3,7 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <fmt/format.h> 5#include <fmt/format.h>
6#include "common/file_util.h" 6#include "common/fs/path_util.h"
7#include "core/file_sys/bis_factory.h" 7#include "core/file_sys/bis_factory.h"
8#include "core/file_sys/mode.h" 8#include "core/file_sys/mode.h"
9#include "core/file_sys/registered_cache.h" 9#include "core/file_sys/registered_cache.h"
@@ -85,7 +85,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
85 VirtualFilesystem file_system) const { 85 VirtualFilesystem file_system) const {
86 auto& keys = Core::Crypto::KeyManager::Instance(); 86 auto& keys = Core::Crypto::KeyManager::Instance();
87 Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( 87 Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
88 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)}; 88 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)};
89 keys.PopulateFromPartitionData(pdm); 89 keys.PopulateFromPartitionData(pdm);
90 90
91 switch (id) { 91 switch (id) {
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h
index 2b4f21073..6c49a64e2 100644
--- a/src/core/file_sys/mode.h
+++ b/src/core/file_sys/mode.h
@@ -10,11 +10,13 @@
10namespace FileSys { 10namespace FileSys {
11 11
12enum class Mode : u32 { 12enum class Mode : u32 {
13 Read = 1, 13 Read = 1 << 0,
14 Write = 2, 14 Write = 1 << 1,
15 ReadWrite = Read | Write, 15 ReadWrite = Read | Write,
16 Append = 4, 16 Append = 1 << 2,
17 ReadAppend = Read | Append,
17 WriteAppend = Write | Append, 18 WriteAppend = Write | Append,
19 All = ReadWrite | Append,
18}; 20};
19 21
20DECLARE_ENUM_FLAG_OPERATORS(Mode) 22DECLARE_ENUM_FLAG_OPERATORS(Mode)
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 48a2ed4d4..c5967049e 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -8,7 +8,6 @@
8#include <iterator> 8#include <iterator>
9#include <utility> 9#include <utility>
10 10
11#include "common/file_util.h"
12#include "common/logging/log.h" 11#include "common/logging/log.h"
13#include "core/file_sys/partition_filesystem.h" 12#include "core/file_sys/partition_filesystem.h"
14#include "core/file_sys/vfs_offset.h" 13#include "core/file_sys/vfs_offset.h"
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index cc9b4b637..53b8b7ca0 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -7,7 +7,6 @@
7#include <cstddef> 7#include <cstddef>
8#include <cstring> 8#include <cstring>
9 9
10#include "common/file_util.h"
11#include "common/hex_util.h" 10#include "common/hex_util.h"
12#include "common/logging/log.h" 11#include "common/logging/log.h"
13#include "common/settings.h" 12#include "common/settings.h"
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index b0cb65952..066c6789a 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -7,7 +7,7 @@
7#include <regex> 7#include <regex>
8#include <mbedtls/sha256.h> 8#include <mbedtls/sha256.h>
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/file_util.h" 10#include "common/fs/path_util.h"
11#include "common/hex_util.h" 11#include "common/hex_util.h"
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "core/crypto/key_manager.h" 13#include "core/crypto/key_manager.h"
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index f497e9396..215e1cb1a 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -5,8 +5,7 @@
5#include <algorithm> 5#include <algorithm>
6#include <numeric> 6#include <numeric>
7#include <string> 7#include <string>
8#include "common/common_paths.h" 8#include "common/fs/path_util.h"
9#include "common/file_util.h"
10#include "common/logging/backend.h" 9#include "common/logging/backend.h"
11#include "core/file_sys/mode.h" 10#include "core/file_sys/mode.h"
12#include "core/file_sys/vfs.h" 11#include "core/file_sys/vfs.h"
@@ -122,15 +121,14 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
122 return nullptr; 121 return nullptr;
123 122
124 for (const auto& file : old_dir->GetFiles()) { 123 for (const auto& file : old_dir->GetFiles()) {
125 const auto x = 124 const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName());
126 CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName());
127 if (x == nullptr) 125 if (x == nullptr)
128 return nullptr; 126 return nullptr;
129 } 127 }
130 128
131 for (const auto& dir : old_dir->GetSubdirectories()) { 129 for (const auto& dir : old_dir->GetSubdirectories()) {
132 const auto x = 130 const auto x =
133 CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName()); 131 CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName());
134 if (x == nullptr) 132 if (x == nullptr)
135 return nullptr; 133 return nullptr;
136 } 134 }
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index 618eb658a..cd162c0c3 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -13,6 +13,7 @@
13#pragma GCC diagnostic pop 13#pragma GCC diagnostic pop
14#endif 14#endif
15 15
16#include "common/fs/path_util.h"
16#include "common/logging/backend.h" 17#include "common/logging/backend.h"
17#include "core/file_sys/vfs.h" 18#include "core/file_sys/vfs.h"
18#include "core/file_sys/vfs_libzip.h" 19#include "core/file_sys/vfs_libzip.h"
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 3d89dd644..d0b8fd046 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -7,8 +7,9 @@
7#include <iterator> 7#include <iterator>
8#include <utility> 8#include <utility>
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/common_paths.h" 10#include "common/fs/file.h"
11#include "common/file_util.h" 11#include "common/fs/fs.h"
12#include "common/fs/path_util.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "core/file_sys/vfs_real.h" 14#include "core/file_sys/vfs_real.h"
14 15
@@ -16,33 +17,31 @@ namespace FileSys {
16 17
17namespace FS = Common::FS; 18namespace FS = Common::FS;
18 19
19static std::string ModeFlagsToString(Mode mode) { 20namespace {
20 std::string mode_str; 21
21 22constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
22 // Calculate the correct open mode for the file. 23 switch (mode) {
23 if (True(mode & Mode::Read) && True(mode & Mode::Write)) { 24 case Mode::Read:
24 if (True(mode & Mode::Append)) { 25 return FS::FileAccessMode::Read;
25 mode_str = "a+"; 26 case Mode::Write:
26 } else { 27 return FS::FileAccessMode::Write;
27 mode_str = "r+"; 28 case Mode::ReadWrite:
28 } 29 return FS::FileAccessMode::ReadWrite;
29 } else { 30 case Mode::Append:
30 if (True(mode & Mode::Read)) { 31 return FS::FileAccessMode::Append;
31 mode_str = "r"; 32 case Mode::ReadAppend:
32 } else if (True(mode & Mode::Append)) { 33 return FS::FileAccessMode::ReadAppend;
33 mode_str = "a"; 34 case Mode::WriteAppend:
34 } else if (True(mode & Mode::Write)) { 35 return FS::FileAccessMode::Append;
35 mode_str = "w"; 36 case Mode::All:
36 } else { 37 return FS::FileAccessMode::ReadAppend;
37 UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode)); 38 default:
38 } 39 return {};
39 } 40 }
40
41 mode_str += "b";
42
43 return mode_str;
44} 41}
45 42
43} // Anonymous namespace
44
46RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} 45RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
47RealVfsFilesystem::~RealVfsFilesystem() = default; 46RealVfsFilesystem::~RealVfsFilesystem() = default;
48 47
@@ -63,7 +62,7 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
63 if (!FS::Exists(path)) { 62 if (!FS::Exists(path)) {
64 return VfsEntryType::None; 63 return VfsEntryType::None;
65 } 64 }
66 if (FS::IsDirectory(path)) { 65 if (FS::IsDir(path)) {
67 return VfsEntryType::Directory; 66 return VfsEntryType::Directory;
68 } 67 }
69 68
@@ -81,12 +80,13 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
81 } 80 }
82 } 81 }
83 82
84 if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { 83 auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
85 FS::CreateEmptyFile(path); 84
85 if (!backing) {
86 return nullptr;
86 } 87 }
87 88
88 auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str()); 89 cache.insert_or_assign(path, std::move(backing));
89 cache.insert_or_assign(path, backing);
90 90
91 // Cannot use make_shared as RealVfsFile constructor is private 91 // Cannot use make_shared as RealVfsFile constructor is private
92 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); 92 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
@@ -94,25 +94,29 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
94 94
95VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 95VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
96 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 96 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
97 const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); 97 // Current usages of CreateFile expect to delete the contents of an existing file.
98 if (!FS::Exists(path)) { 98 if (FS::IsFile(path)) {
99 FS::CreateFullPath(path_fwd); 99 FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
100 if (!FS::CreateEmptyFile(path)) { 100
101 if (!temp.IsOpen()) {
101 return nullptr; 102 return nullptr;
102 } 103 }
104
105 temp.Close();
106
107 return OpenFile(path, perms);
108 }
109
110 if (!FS::NewFile(path)) {
111 return nullptr;
103 } 112 }
113
104 return OpenFile(path, perms); 114 return OpenFile(path, perms);
105} 115}
106 116
107VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { 117VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
108 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); 118 // Unused
109 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); 119 return nullptr;
110
111 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
112 !FS::Copy(old_path, new_path)) {
113 return nullptr;
114 }
115 return OpenFile(new_path, Mode::ReadWrite);
116} 120}
117 121
118VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { 122VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
@@ -127,13 +131,13 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
127 file->Close(); 131 file->Close();
128 } 132 }
129 133
130 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || 134 if (!FS::RenameFile(old_path, new_path)) {
131 !FS::Rename(old_path, new_path)) {
132 return nullptr; 135 return nullptr;
133 } 136 }
134 137
135 cache.erase(old_path); 138 cache.erase(old_path);
136 if (file->Open(new_path, "r+b")) { 139 file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
140 if (file->IsOpen()) {
137 cache.insert_or_assign(new_path, std::move(file)); 141 cache.insert_or_assign(new_path, std::move(file));
138 } else { 142 } else {
139 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); 143 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path);
@@ -157,7 +161,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
157 cache.erase(path); 161 cache.erase(path);
158 } 162 }
159 163
160 return FS::Delete(path); 164 return FS::RemoveFile(path);
161} 165}
162 166
163VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { 167VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
@@ -168,12 +172,8 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
168 172
169VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { 173VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
170 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); 174 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
171 const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); 175 if (!FS::CreateDirs(path)) {
172 if (!FS::Exists(path)) { 176 return nullptr;
173 FS::CreateFullPath(path_fwd);
174 if (!FS::CreateDir(path)) {
175 return nullptr;
176 }
177 } 177 }
178 // Cannot use make_shared as RealVfsDirectory constructor is private 178 // Cannot use make_shared as RealVfsDirectory constructor is private
179 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 179 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
@@ -181,13 +181,8 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
181 181
182VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, 182VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
183 std::string_view new_path_) { 183 std::string_view new_path_) {
184 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); 184 // Unused
185 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); 185 return nullptr;
186 if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) {
187 return nullptr;
188 }
189 FS::CopyDir(old_path, new_path);
190 return OpenDirectory(new_path, Mode::ReadWrite);
191} 186}
192 187
193VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, 188VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
@@ -195,8 +190,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
195 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); 190 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
196 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); 191 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
197 192
198 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || 193 if (!FS::RenameDir(old_path, new_path)) {
199 !FS::Rename(old_path, new_path)) {
200 return nullptr; 194 return nullptr;
201 } 195 }
202 196
@@ -208,7 +202,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
208 202
209 const auto file_old_path = 203 const auto file_old_path =
210 FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); 204 FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault);
211 auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), 205 auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()),
212 FS::DirectorySeparator::PlatformDefault); 206 FS::DirectorySeparator::PlatformDefault);
213 const auto& cached = cache[file_old_path]; 207 const auto& cached = cache[file_old_path];
214 208
@@ -218,7 +212,8 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
218 212
219 auto file = cached.lock(); 213 auto file = cached.lock();
220 cache.erase(file_old_path); 214 cache.erase(file_old_path);
221 if (file->Open(file_new_path, "r+b")) { 215 file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
216 if (file->IsOpen()) {
222 cache.insert_or_assign(std::move(file_new_path), std::move(file)); 217 cache.insert_or_assign(std::move(file_new_path), std::move(file));
223 } else { 218 } else {
224 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); 219 LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path);
@@ -245,15 +240,13 @@ bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
245 cache.erase(kv.first); 240 cache.erase(kv.first);
246 } 241 }
247 242
248 return FS::DeleteDirRecursively(path); 243 return FS::RemoveDirRecursively(path);
249} 244}
250 245
251RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, 246RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_,
252 const std::string& path_, Mode perms_) 247 const std::string& path_, Mode perms_)
253 : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), 248 : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)),
254 path_components(FS::SplitPathComponents(path_)), 249 path_components(FS::SplitPathComponents(path_)), perms(perms_) {}
255 parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
256 perms(perms_) {}
257 250
258RealVfsFile::~RealVfsFile() = default; 251RealVfsFile::~RealVfsFile() = default;
259 252
@@ -266,7 +259,7 @@ std::size_t RealVfsFile::GetSize() const {
266} 259}
267 260
268bool RealVfsFile::Resize(std::size_t new_size) { 261bool RealVfsFile::Resize(std::size_t new_size) {
269 return backing->Resize(new_size); 262 return backing->SetSize(new_size);
270} 263}
271 264
272VirtualDir RealVfsFile::GetContainingDirectory() const { 265VirtualDir RealVfsFile::GetContainingDirectory() const {
@@ -274,33 +267,33 @@ VirtualDir RealVfsFile::GetContainingDirectory() const {
274} 267}
275 268
276bool RealVfsFile::IsWritable() const { 269bool RealVfsFile::IsWritable() const {
277 return True(perms & Mode::WriteAppend); 270 return True(perms & Mode::Write);
278} 271}
279 272
280bool RealVfsFile::IsReadable() const { 273bool RealVfsFile::IsReadable() const {
281 return True(perms & Mode::ReadWrite); 274 return True(perms & Mode::Read);
282} 275}
283 276
284std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 277std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
285 if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { 278 if (!backing->Seek(static_cast<s64>(offset))) {
286 return 0; 279 return 0;
287 } 280 }
288 return backing->ReadBytes(data, length); 281 return backing->ReadSpan(std::span{data, length});
289} 282}
290 283
291std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 284std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
292 if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { 285 if (!backing->Seek(static_cast<s64>(offset))) {
293 return 0; 286 return 0;
294 } 287 }
295 return backing->WriteBytes(data, length); 288 return backing->WriteSpan(std::span{data, length});
296} 289}
297 290
298bool RealVfsFile::Rename(std::string_view name) { 291bool RealVfsFile::Rename(std::string_view name) {
299 return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr; 292 return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
300} 293}
301 294
302bool RealVfsFile::Close() { 295void RealVfsFile::Close() {
303 return backing->Close(); 296 backing->Close();
304} 297}
305 298
306// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if 299// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
@@ -313,15 +306,16 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
313 } 306 }
314 307
315 std::vector<VirtualFile> out; 308 std::vector<VirtualFile> out;
316 FS::ForeachDirectoryEntry( 309
317 nullptr, path, 310 const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
318 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { 311 const auto full_path_string = FS::PathToUTF8String(full_path);
319 const std::string full_path = directory + DIR_SEP + filename; 312
320 if (!FS::IsDirectory(full_path)) { 313 out.emplace_back(base.OpenFile(full_path_string, perms));
321 out.emplace_back(base.OpenFile(full_path, perms)); 314
322 } 315 return true;
323 return true; 316 };
324 }); 317
318 FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File);
325 319
326 return out; 320 return out;
327} 321}
@@ -333,42 +327,41 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
333 } 327 }
334 328
335 std::vector<VirtualDir> out; 329 std::vector<VirtualDir> out;
336 FS::ForeachDirectoryEntry( 330
337 nullptr, path, 331 const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) {
338 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { 332 const auto full_path_string = FS::PathToUTF8String(full_path);
339 const std::string full_path = directory + DIR_SEP + filename; 333
340 if (FS::IsDirectory(full_path)) { 334 out.emplace_back(base.OpenDirectory(full_path_string, perms));
341 out.emplace_back(base.OpenDirectory(full_path, perms)); 335
342 } 336 return true;
343 return true; 337 };
344 }); 338
339 FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory);
345 340
346 return out; 341 return out;
347} 342}
348 343
349RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) 344RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
350 : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), 345 : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
351 path_components(FS::SplitPathComponents(path)), 346 path_components(FS::SplitPathComponents(path)), perms(perms_) {
352 parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), 347 if (!FS::Exists(path) && True(perms & Mode::Write)) {
353 perms(perms_) { 348 void(FS::CreateDirs(path));
354 if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
355 FS::CreateDir(path);
356 } 349 }
357} 350}
358 351
359RealVfsDirectory::~RealVfsDirectory() = default; 352RealVfsDirectory::~RealVfsDirectory() = default;
360 353
361VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { 354VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
362 const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); 355 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
363 if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { 356 if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
364 return nullptr; 357 return nullptr;
365 } 358 }
366 return base.OpenFile(full_path, perms); 359 return base.OpenFile(full_path, perms);
367} 360}
368 361
369VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { 362VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
370 const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); 363 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
371 if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { 364 if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
372 return nullptr; 365 return nullptr;
373 } 366 }
374 return base.OpenDirectory(full_path, perms); 367 return base.OpenDirectory(full_path, perms);
@@ -383,17 +376,20 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
383} 376}
384 377
385VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { 378VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
386 const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); 379 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
380 if (!FS::CreateParentDirs(full_path)) {
381 return nullptr;
382 }
387 return base.CreateFile(full_path, perms); 383 return base.CreateFile(full_path, perms);
388} 384}
389 385
390VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) { 386VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) {
391 const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); 387 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
392 return base.CreateDirectory(full_path, perms); 388 return base.CreateDirectory(full_path, perms);
393} 389}
394 390
395bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { 391bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
396 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name)); 392 const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
397 return base.DeleteDirectory(full_path); 393 return base.DeleteDirectory(full_path);
398} 394}
399 395
@@ -406,11 +402,11 @@ std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
406} 402}
407 403
408bool RealVfsDirectory::IsWritable() const { 404bool RealVfsDirectory::IsWritable() const {
409 return True(perms & Mode::WriteAppend); 405 return True(perms & Mode::Write);
410} 406}
411 407
412bool RealVfsDirectory::IsReadable() const { 408bool RealVfsDirectory::IsReadable() const {
413 return True(perms & Mode::ReadWrite); 409 return True(perms & Mode::Read);
414} 410}
415 411
416std::string RealVfsDirectory::GetName() const { 412std::string RealVfsDirectory::GetName() const {
@@ -426,27 +422,27 @@ VirtualDir RealVfsDirectory::GetParentDirectory() const {
426} 422}
427 423
428VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { 424VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
429 const std::string subdir_path = (path + DIR_SEP).append(name); 425 const std::string subdir_path = (path + '/').append(name);
430 return base.CreateDirectory(subdir_path, perms); 426 return base.CreateDirectory(subdir_path, perms);
431} 427}
432 428
433VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { 429VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
434 const std::string file_path = (path + DIR_SEP).append(name); 430 const std::string file_path = (path + '/').append(name);
435 return base.CreateFile(file_path, perms); 431 return base.CreateFile(file_path, perms);
436} 432}
437 433
438bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { 434bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
439 const std::string subdir_path = (path + DIR_SEP).append(name); 435 const std::string subdir_path = (path + '/').append(name);
440 return base.DeleteDirectory(subdir_path); 436 return base.DeleteDirectory(subdir_path);
441} 437}
442 438
443bool RealVfsDirectory::DeleteFile(std::string_view name) { 439bool RealVfsDirectory::DeleteFile(std::string_view name) {
444 const std::string file_path = (path + DIR_SEP).append(name); 440 const std::string file_path = (path + '/').append(name);
445 return base.DeleteFile(file_path); 441 return base.DeleteFile(file_path);
446} 442}
447 443
448bool RealVfsDirectory::Rename(std::string_view name) { 444bool RealVfsDirectory::Rename(std::string_view name) {
449 const std::string new_name = (parent_path + DIR_SEP).append(name); 445 const std::string new_name = (parent_path + '/').append(name);
450 return base.MoveFile(path, new_name) != nullptr; 446 return base.MoveFile(path, new_name) != nullptr;
451} 447}
452 448
@@ -462,14 +458,17 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
462 } 458 }
463 459
464 std::map<std::string, VfsEntryType, std::less<>> out; 460 std::map<std::string, VfsEntryType, std::less<>> out;
465 FS::ForeachDirectoryEntry( 461
466 nullptr, path, 462 const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) {
467 [&out](u64* entries_out, const std::string& directory, const std::string& filename) { 463 const auto filename = FS::PathToUTF8String(full_path.filename());
468 const std::string full_path = directory + DIR_SEP + filename; 464
469 out.emplace(filename, 465 out.insert_or_assign(filename,
470 FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File); 466 FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
471 return true; 467
472 }); 468 return true;
469 };
470
471 FS::IterateDirEntries(path, callback);
473 472
474 return out; 473 return out;
475} 474}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 0666f2679..e4d1bba79 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -61,14 +61,13 @@ private:
61 RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, 61 RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing,
62 const std::string& path, Mode perms = Mode::Read); 62 const std::string& path, Mode perms = Mode::Read);
63 63
64 bool Close(); 64 void Close();
65 65
66 RealVfsFilesystem& base; 66 RealVfsFilesystem& base;
67 std::shared_ptr<Common::FS::IOFile> backing; 67 std::shared_ptr<Common::FS::IOFile> backing;
68 std::string path; 68 std::string path;
69 std::string parent_path; 69 std::string parent_path;
70 std::vector<std::string> path_components; 70 std::vector<std::string> path_components;
71 std::vector<std::string> parent_components;
72 Mode perms; 71 Mode perms;
73}; 72};
74 73
@@ -110,7 +109,6 @@ private:
110 std::string path; 109 std::string path;
111 std::string parent_path; 110 std::string parent_path;
112 std::vector<std::string> path_components; 111 std::vector<std::string> path_components;
113 std::vector<std::string> parent_components;
114 Mode perms; 112 Mode perms;
115}; 113};
116 114
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index 814fd5680..d6fe1af47 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -11,7 +11,7 @@
11#include <mbedtls/md.h> 11#include <mbedtls/md.h>
12#include <mbedtls/sha256.h> 12#include <mbedtls/sha256.h>
13 13
14#include "common/file_util.h" 14#include "common/fs/path_util.h"
15#include "common/hex_util.h" 15#include "common/hex_util.h"
16#include "common/string_util.h" 16#include "common/string_util.h"
17#include "core/crypto/aes_util.h" 17#include "core/crypto/aes_util.h"
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 49c09a570..39cd1efc1 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -4,9 +4,9 @@
4 4
5#include <algorithm> 5#include <algorithm>
6#include <array> 6#include <array>
7#include "common/common_paths.h"
8#include "common/common_types.h" 7#include "common/common_types.h"
9#include "common/file_util.h" 8#include "common/fs/file.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/string_util.h" 11#include "common/string_util.h"
12#include "common/swap.h" 12#include "common/swap.h"
@@ -41,9 +41,9 @@ constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
41// Thumbnails are hard coded to be at least this size 41// Thumbnails are hard coded to be at least this size
42constexpr std::size_t THUMBNAIL_SIZE = 0x24000; 42constexpr std::size_t THUMBNAIL_SIZE = 0x24000;
43 43
44static std::string GetImagePath(Common::UUID uuid) { 44static std::filesystem::path GetImagePath(Common::UUID uuid) {
45 return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 45 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
46 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 46 fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch());
47} 47}
48 48
49static constexpr u32 SanitizeJPEGSize(std::size_t size) { 49static constexpr u32 SanitizeJPEGSize(std::size_t size) {
@@ -328,7 +328,8 @@ protected:
328 IPC::ResponseBuilder rb{ctx, 3}; 328 IPC::ResponseBuilder rb{ctx, 3};
329 rb.Push(RESULT_SUCCESS); 329 rb.Push(RESULT_SUCCESS);
330 330
331 const Common::FS::IOFile image(GetImagePath(user_id), "rb"); 331 const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read,
332 Common::FS::FileType::BinaryFile);
332 if (!image.IsOpen()) { 333 if (!image.IsOpen()) {
333 LOG_WARNING(Service_ACC, 334 LOG_WARNING(Service_ACC,
334 "Failed to load user provided image! Falling back to built-in backup..."); 335 "Failed to load user provided image! Falling back to built-in backup...");
@@ -339,7 +340,10 @@ protected:
339 340
340 const u32 size = SanitizeJPEGSize(image.GetSize()); 341 const u32 size = SanitizeJPEGSize(image.GetSize());
341 std::vector<u8> buffer(size); 342 std::vector<u8> buffer(size);
342 image.ReadBytes(buffer.data(), buffer.size()); 343
344 if (image.Read(buffer) != buffer.size()) {
345 LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image.");
346 }
343 347
344 ctx.WriteBuffer(buffer); 348 ctx.WriteBuffer(buffer);
345 rb.Push<u32>(size); 349 rb.Push<u32>(size);
@@ -350,7 +354,8 @@ protected:
350 IPC::ResponseBuilder rb{ctx, 3}; 354 IPC::ResponseBuilder rb{ctx, 3};
351 rb.Push(RESULT_SUCCESS); 355 rb.Push(RESULT_SUCCESS);
352 356
353 const Common::FS::IOFile image(GetImagePath(user_id), "rb"); 357 const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read,
358 Common::FS::FileType::BinaryFile);
354 359
355 if (!image.IsOpen()) { 360 if (!image.IsOpen()) {
356 LOG_WARNING(Service_ACC, 361 LOG_WARNING(Service_ACC,
@@ -415,10 +420,11 @@ protected:
415 ProfileData data; 420 ProfileData data;
416 std::memcpy(&data, user_data.data(), sizeof(ProfileData)); 421 std::memcpy(&data, user_data.data(), sizeof(ProfileData));
417 422
418 Common::FS::IOFile image(GetImagePath(user_id), "wb"); 423 Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Write,
424 Common::FS::FileType::BinaryFile);
419 425
420 if (!image.IsOpen() || !image.Resize(image_data.size()) || 426 if (!image.IsOpen() || !image.SetSize(image_data.size()) ||
421 image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || 427 image.Write(image_data) != image_data.size() ||
422 !profile_manager.SetProfileBaseAndData(user_id, base, data)) { 428 !profile_manager.SetProfileBaseAndData(user_id, base, data)) {
423 LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); 429 LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!");
424 IPC::ResponseBuilder rb{ctx, 2}; 430 IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index de83d82a4..77510489c 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -7,7 +7,9 @@
7 7
8#include <fmt/format.h> 8#include <fmt/format.h>
9 9
10#include "common/file_util.h" 10#include "common/fs/file.h"
11#include "common/fs/fs.h"
12#include "common/fs/path_util.h"
11#include "common/settings.h" 13#include "common/settings.h"
12#include "core/hle/service/acc/profile_manager.h" 14#include "core/hle/service/acc/profile_manager.h"
13 15
@@ -36,7 +38,7 @@ constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1));
36constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2)); 38constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2));
37constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); 39constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
38 40
39constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/"; 41constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "system/save/8000000000000010/su/avators";
40 42
41ProfileManager::ProfileManager() { 43ProfileManager::ProfileManager() {
42 ParseUserSaveFile(); 44 ParseUserSaveFile();
@@ -325,8 +327,9 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
325} 327}
326 328
327void ProfileManager::ParseUserSaveFile() { 329void ProfileManager::ParseUserSaveFile() {
328 const FS::IOFile save( 330 const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
329 FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb"); 331 "profiles.dat");
332 const FS::IOFile save(save_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
330 333
331 if (!save.IsOpen()) { 334 if (!save.IsOpen()) {
332 LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " 335 LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
@@ -335,7 +338,7 @@ void ProfileManager::ParseUserSaveFile() {
335 } 338 }
336 339
337 ProfileDataRaw data; 340 ProfileDataRaw data;
338 if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) { 341 if (!save.ReadObject(data)) {
339 LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user " 342 LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
340 "'yuzu' with random UUID."); 343 "'yuzu' with random UUID.");
341 return; 344 return;
@@ -372,31 +375,27 @@ void ProfileManager::WriteUserSaveFile() {
372 }; 375 };
373 } 376 }
374 377
375 const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010"; 378 const auto raw_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / "system/save/8000000000000010");
376 if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) { 379 if (FS::IsFile(raw_path) && !FS::RemoveFile(raw_path)) {
377 FS::Delete(raw_path); 380 return;
378 } 381 }
379 382
380 const auto path = 383 const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
381 FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; 384 "profiles.dat");
382 385
383 if (!FS::CreateFullPath(path)) { 386 if (!FS::CreateParentDirs(save_path)) {
384 LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " 387 LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
385 "nand/system/save/8000000000000010/su/avators to mitigate this " 388 "nand/system/save/8000000000000010/su/avators to mitigate this "
386 "issue."); 389 "issue.");
387 return; 390 return;
388 } 391 }
389 392
390 FS::IOFile save(path, "wb"); 393 FS::IOFile save(save_path, FS::FileAccessMode::Write, FS::FileType::BinaryFile);
391 394
392 if (!save.IsOpen()) { 395 if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) {
393 LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " 396 LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
394 "made in current session will be saved."); 397 "made in current session will be saved.");
395 return;
396 } 398 }
397
398 save.Resize(sizeof(ProfileDataRaw));
399 save.WriteBytes(&raw, sizeof(ProfileDataRaw));
400} 399}
401 400
402}; // namespace Service::Account 401}; // namespace Service::Account
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index e5f4a4485..3b28e829b 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -3,8 +3,9 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_paths.h" 6#include "common/fs/file.h"
7#include "common/file_util.h" 7#include "common/fs/fs.h"
8#include "common/fs/path_util.h"
8#include "common/logging/log.h" 9#include "common/logging/log.h"
9#include "common/string_util.h" 10#include "common/string_util.h"
10#include "core/core.h" 11#include "core/core.h"
@@ -135,14 +136,10 @@ void ExtractSharedFonts(Core::System& system) {
135 "FontNintendoExtended2.ttf", 136 "FontNintendoExtended2.ttf",
136 }; 137 };
137 138
138 for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { 139 const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts";
139 const auto fonts_dir = Common::FS::SanitizePath(
140 fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
141 Common::FS::DirectorySeparator::PlatformDefault);
142 140
143 const auto font_file_path = 141 for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
144 Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]), 142 const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i];
145 Common::FS::DirectorySeparator::PlatformDefault);
146 143
147 if (Common::FS::Exists(font_file_path)) { 144 if (Common::FS::Exists(font_file_path)) {
148 continue; 145 continue;
@@ -197,8 +194,8 @@ void ExtractSharedFonts(Core::System& system) {
197 FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>( 194 FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
198 std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); 195 std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
199 196
200 const auto temp_dir = 197 const auto temp_dir = system.GetFilesystem()->CreateDirectory(
201 system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite); 198 Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite);
202 199
203 const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); 200 const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
204 201
@@ -312,13 +309,14 @@ void WebBrowser::Execute() {
312} 309}
313 310
314void WebBrowser::ExtractOfflineRomFS() { 311void WebBrowser::ExtractOfflineRomFS() {
315 LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir); 312 LOG_DEBUG(Service_AM, "Extracting RomFS to {}",
313 Common::FS::PathToUTF8String(offline_cache_dir));
316 314
317 const auto extracted_romfs_dir = 315 const auto extracted_romfs_dir =
318 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); 316 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
319 317
320 const auto temp_dir = 318 const auto temp_dir = system.GetFilesystem()->CreateDirectory(
321 system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite); 319 Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite);
322 320
323 FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); 321 FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
324} 322}
@@ -397,15 +395,12 @@ void WebBrowser::InitializeOffline() {
397 "system_data", 395 "system_data",
398 }; 396 };
399 397
400 offline_cache_dir = Common::FS::SanitizePath( 398 offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
401 fmt::format("{}/offline_web_applet_{}/{:016X}", 399 fmt::format("offline_web_applet_{}/{:016X}",
402 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), 400 RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id);
403 RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id),
404 Common::FS::DirectorySeparator::PlatformDefault);
405 401
406 offline_document = Common::FS::SanitizePath( 402 offline_document = Common::FS::ConcatPathSafe(
407 fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path), 403 offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path));
408 Common::FS::DirectorySeparator::PlatformDefault);
409} 404}
410 405
411void WebBrowser::InitializeShare() {} 406void WebBrowser::InitializeShare() {}
@@ -429,8 +424,7 @@ void WebBrowser::ExecuteLogin() {
429} 424}
430 425
431void WebBrowser::ExecuteOffline() { 426void WebBrowser::ExecuteOffline() {
432 const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document), 427 const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document));
433 Common::FS::DirectorySeparator::PlatformDefault);
434 428
435 if (!Common::FS::Exists(main_url)) { 429 if (!Common::FS::Exists(main_url)) {
436 offline_romfs = GetOfflineRomFS(system, title_id, nca_type); 430 offline_romfs = GetOfflineRomFS(system, title_id, nca_type);
@@ -444,10 +438,11 @@ void WebBrowser::ExecuteOffline() {
444 } 438 }
445 } 439 }
446 440
447 LOG_INFO(Service_AM, "Opening offline document at {}", offline_document); 441 LOG_INFO(Service_AM, "Opening offline document at {}",
442 Common::FS::PathToUTF8String(offline_document));
448 443
449 frontend.OpenLocalWebPage( 444 frontend.OpenLocalWebPage(
450 offline_document, [this] { ExtractOfflineRomFS(); }, 445 Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); },
451 [this](WebExitReason exit_reason, std::string last_url) { 446 [this](WebExitReason exit_reason, std::string last_url) {
452 WebBrowserExit(exit_reason, last_url); 447 WebBrowserExit(exit_reason, last_url);
453 }); 448 });
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 1e1812f36..cdeaf2c40 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <filesystem>
7#include <optional> 8#include <optional>
8 9
9#include "common/common_funcs.h" 10#include "common/common_funcs.h"
@@ -75,8 +76,8 @@ private:
75 76
76 u64 title_id{}; 77 u64 title_id{};
77 FileSys::ContentRecordType nca_type{}; 78 FileSys::ContentRecordType nca_type{};
78 std::string offline_cache_dir; 79 std::filesystem::path offline_cache_dir;
79 std::string offline_document; 80 std::filesystem::path offline_document;
80 FileSys::VirtualFile offline_romfs; 81 FileSys::VirtualFile offline_romfs;
81 82
82 std::string external_url; 83 std::string external_url;
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index d6d2f52e5..3cc397604 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -15,6 +15,9 @@
15#pragma GCC diagnostic pop 15#pragma GCC diagnostic pop
16#endif 16#endif
17 17
18#include "common/fs/file.h"
19#include "common/fs/fs.h"
20#include "common/fs/path_util.h"
18#include "common/hex_util.h" 21#include "common/hex_util.h"
19#include "common/logging/backend.h" 22#include "common/logging/backend.h"
20#include "common/logging/log.h" 23#include "common/logging/log.h"
@@ -96,14 +99,14 @@ constexpr u32 PORT = 443;
96constexpr u32 TIMEOUT_SECONDS = 30; 99constexpr u32 TIMEOUT_SECONDS = 30;
97[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB 100[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
98 101
99std::string GetBINFilePath(u64 title_id) { 102std::filesystem::path GetBINFilePath(u64 title_id) {
100 return fmt::format("{}bcat/{:016X}/launchparam.bin", 103 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" /
101 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); 104 fmt::format("{:016X}/launchparam.bin", title_id);
102} 105}
103 106
104std::string GetZIPFilePath(u64 title_id) { 107std::filesystem::path GetZIPFilePath(u64 title_id) {
105 return fmt::format("{}bcat/{:016X}/data.zip", 108 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" /
106 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); 109 fmt::format("{:016X}/data.zip", title_id);
107} 110}
108 111
109// If the error is something the user should know about (build ID mismatch, bad client version), 112// If the error is something the user should know about (build ID mismatch, bad client version),
@@ -187,7 +190,7 @@ bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
187 190
188class Boxcat::Client { 191class Boxcat::Client {
189public: 192public:
190 Client(std::string path_, u64 title_id_, u64 build_id_) 193 Client(std::filesystem::path path_, u64 title_id_, u64 build_id_)
191 : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {} 194 : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {}
192 195
193 DownloadResult DownloadDataZip() { 196 DownloadResult DownloadDataZip() {
@@ -217,10 +220,11 @@ private:
217 }; 220 };
218 221
219 if (Common::FS::Exists(path)) { 222 if (Common::FS::Exists(path)) {
220 Common::FS::IOFile file{path, "rb"}; 223 Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
224 Common::FS::FileType::BinaryFile};
221 if (file.IsOpen()) { 225 if (file.IsOpen()) {
222 std::vector<u8> bytes(file.GetSize()); 226 std::vector<u8> bytes(file.GetSize());
223 file.ReadBytes(bytes.data(), bytes.size()); 227 void(file.Read(bytes));
224 const auto digest = DigestFile(bytes); 228 const auto digest = DigestFile(bytes);
225 headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); 229 headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
226 } 230 }
@@ -247,14 +251,23 @@ private:
247 return DownloadResult::InvalidContentType; 251 return DownloadResult::InvalidContentType;
248 } 252 }
249 253
250 Common::FS::CreateFullPath(path); 254 if (!Common::FS::CreateDirs(path)) {
251 Common::FS::IOFile file{path, "wb"};
252 if (!file.IsOpen())
253 return DownloadResult::GeneralFSError; 255 return DownloadResult::GeneralFSError;
254 if (!file.Resize(response->body.size())) 256 }
257
258 Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append,
259 Common::FS::FileType::BinaryFile};
260 if (!file.IsOpen()) {
261 return DownloadResult::GeneralFSError;
262 }
263
264 if (!file.SetSize(response->body.size())) {
255 return DownloadResult::GeneralFSError; 265 return DownloadResult::GeneralFSError;
256 if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size()) 266 }
267
268 if (file.Write(response->body) != response->body.size()) {
257 return DownloadResult::GeneralFSError; 269 return DownloadResult::GeneralFSError;
270 }
258 271
259 return DownloadResult::Success; 272 return DownloadResult::Success;
260 } 273 }
@@ -267,7 +280,7 @@ private:
267 } 280 }
268 281
269 std::unique_ptr<httplib::SSLClient> client; 282 std::unique_ptr<httplib::SSLClient> client;
270 std::string path; 283 std::filesystem::path path;
271 u64 title_id; 284 u64 title_id;
272 u64 build_id; 285 u64 build_id;
273}; 286};
@@ -291,7 +304,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
291 return; 304 return;
292 } 305 }
293 306
294 const auto zip_path{GetZIPFilePath(title.title_id)}; 307 const auto zip_path = GetZIPFilePath(title.title_id);
295 Boxcat::Client client{zip_path, title.title_id, title.build_id}; 308 Boxcat::Client client{zip_path, title.title_id, title.build_id};
296 309
297 progress.StartConnecting(); 310 progress.StartConnecting();
@@ -301,7 +314,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
301 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 314 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
302 315
303 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 316 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
304 Common::FS::Delete(zip_path); 317 void(Common::FS::RemoveFile(zip_path));
305 } 318 }
306 319
307 HandleDownloadDisplayResult(applet_manager, res); 320 HandleDownloadDisplayResult(applet_manager, res);
@@ -311,11 +324,13 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
311 324
312 progress.StartProcessingDataList(); 325 progress.StartProcessingDataList();
313 326
314 Common::FS::IOFile zip{zip_path, "rb"}; 327 Common::FS::IOFile zip{zip_path, Common::FS::FileAccessMode::Read,
328 Common::FS::FileType::BinaryFile};
315 const auto size = zip.GetSize(); 329 const auto size = zip.GetSize();
316 std::vector<u8> bytes(size); 330 std::vector<u8> bytes(size);
317 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { 331 if (!zip.IsOpen() || size == 0 || zip.Read(bytes) != bytes.size()) {
318 LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); 332 LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!",
333 Common::FS::PathToUTF8String(zip_path));
319 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); 334 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
320 return; 335 return;
321 } 336 }
@@ -419,19 +434,19 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
419} 434}
420 435
421std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { 436std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
422 const auto path{GetBINFilePath(title.title_id)}; 437 const auto bin_file_path = GetBINFilePath(title.title_id);
423 438
424 if (Settings::values.bcat_boxcat_local) { 439 if (Settings::values.bcat_boxcat_local) {
425 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); 440 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
426 } else { 441 } else {
427 Client launch_client{path, title.title_id, title.build_id}; 442 Client launch_client{bin_file_path, title.title_id, title.build_id};
428 443
429 const auto res = launch_client.DownloadLaunchParam(); 444 const auto res = launch_client.DownloadLaunchParam();
430 if (res != DownloadResult::Success) { 445 if (res != DownloadResult::Success) {
431 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 446 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
432 447
433 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 448 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
434 Common::FS::Delete(path); 449 void(Common::FS::RemoveFile(bin_file_path));
435 } 450 }
436 451
437 HandleDownloadDisplayResult(applet_manager, res); 452 HandleDownloadDisplayResult(applet_manager, res);
@@ -439,12 +454,13 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
439 } 454 }
440 } 455 }
441 456
442 Common::FS::IOFile bin{path, "rb"}; 457 Common::FS::IOFile bin{bin_file_path, Common::FS::FileAccessMode::Read,
458 Common::FS::FileType::BinaryFile};
443 const auto size = bin.GetSize(); 459 const auto size = bin.GetSize();
444 std::vector<u8> bytes(size); 460 std::vector<u8> bytes(size);
445 if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { 461 if (!bin.IsOpen() || size == 0 || bin.Read(bytes) != bytes.size()) {
446 LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", 462 LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
447 path); 463 Common::FS::PathToUTF8String(bin_file_path));
448 return std::nullopt; 464 return std::nullopt;
449 } 465 }
450 466
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index 432abde76..b7666e95a 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -6,7 +6,6 @@
6#include <cstring> 6#include <cstring>
7#include <ctime> 7#include <ctime>
8#include <fmt/chrono.h> 8#include <fmt/chrono.h>
9#include "common/file_util.h"
10#include "common/logging/log.h" 9#include "common/logging/log.h"
11#include "common/scm_rev.h" 10#include "common/scm_rev.h"
12#include "common/swap.h" 11#include "common/swap.h"
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 67baaee9b..78664439d 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -5,7 +5,7 @@
5#include <utility> 5#include <utility>
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/file_util.h" 8#include "common/fs/path_util.h"
9#include "common/settings.h" 9#include "common/settings.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/file_sys/bis_factory.h" 11#include "core/file_sys/bis_factory.h"
@@ -728,14 +728,17 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
728 sdmc_factory = nullptr; 728 sdmc_factory = nullptr;
729 } 729 }
730 730
731 auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir), 731 using YuzuPath = Common::FS::YuzuPath;
732 FileSys::Mode::ReadWrite); 732 const auto rw_mode = FileSys::Mode::ReadWrite;
733 auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), 733
734 FileSys::Mode::ReadWrite); 734 auto nand_directory =
735 auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), 735 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
736 FileSys::Mode::ReadWrite); 736 auto sd_directory =
737 auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), 737 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode);
738 FileSys::Mode::ReadWrite); 738 auto load_directory =
739 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read);
740 auto dump_directory =
741 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);
739 742
740 if (bis_factory == nullptr) { 743 if (bis_factory == nullptr) {
741 bis_factory = 744 bis_factory =
diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp
index 70350a2a3..114aff31c 100644
--- a/src/core/hle/service/mii/manager.cpp
+++ b/src/core/hle/service/mii/manager.cpp
@@ -6,7 +6,6 @@
6#include <random> 6#include <random>
7 7
8#include "common/assert.h" 8#include "common/assert.h"
9#include "common/file_util.h"
10#include "common/logging/log.h" 9#include "common/logging/log.h"
11#include "common/string_util.h" 10#include "common/string_util.h"
12 11
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index e14acce58..90ba5c752 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -7,9 +7,7 @@
7#include <vector> 7#include <vector>
8 8
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/common_paths.h"
11#include "common/common_types.h" 10#include "common/common_types.h"
12#include "common/file_util.h"
13#include "common/logging/log.h" 11#include "common/logging/log.h"
14#include "common/swap.h" 12#include "common/swap.h"
15#include "core/core.h" 13#include "core/core.h"
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 022885c1b..a19bb220a 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -5,7 +5,6 @@
5#include <cinttypes> 5#include <cinttypes>
6#include <cstring> 6#include <cstring>
7#include "common/common_funcs.h" 7#include "common/common_funcs.h"
8#include "common/file_util.h"
9#include "common/logging/log.h" 8#include "common/logging/log.h"
10#include "core/core.h" 9#include "core/core.h"
11#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index c062a4259..3d9276f15 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -7,7 +7,6 @@
7#include <string> 7#include <string>
8#include "common/common_funcs.h" 8#include "common/common_funcs.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/file_util.h"
11#include "common/logging/log.h" 10#include "common/logging/log.h"
12#include "core/hle/kernel/code_set.h" 11#include "core/hle/kernel/code_set.h"
13#include "core/hle/kernel/k_page_table.h" 12#include "core/hle/kernel/k_page_table.h"
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index d4808fb5b..228dc6389 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -7,7 +7,7 @@
7#include <ostream> 7#include <ostream>
8#include <string> 8#include <string>
9#include "common/concepts.h" 9#include "common/concepts.h"
10#include "common/file_util.h" 10#include "common/fs/path_util.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/string_util.h" 12#include "common/string_util.h"
13#include "core/core.h" 13#include "core/core.h"
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 418cbf61b..aa51b0daa 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -4,7 +4,6 @@
4 4
5#include <utility> 5#include <utility>
6 6
7#include "common/file_util.h"
8#include "common/logging/log.h" 7#include "common/logging/log.h"
9#include "core/core.h" 8#include "core/core.h"
10#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index ef54fa574..618555202 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -7,7 +7,6 @@
7 7
8#include "common/common_funcs.h" 8#include "common/common_funcs.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/file_util.h"
11#include "common/logging/log.h" 10#include "common/logging/log.h"
12#include "common/settings.h" 11#include "common/settings.h"
13#include "common/swap.h" 12#include "common/swap.h"
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index df59412cf..0f5cfda68 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -7,7 +7,6 @@
7#include <vector> 7#include <vector>
8 8
9#include "common/common_funcs.h" 9#include "common/common_funcs.h"
10#include "common/file_util.h"
11#include "common/hex_util.h" 10#include "common/hex_util.h"
12#include "common/logging/log.h" 11#include "common/logging/log.h"
13#include "common/lz4_compression.h" 12#include "common/lz4_compression.h"
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index c42c437b7..6635a1339 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -11,7 +11,9 @@
11#include <thread> 11#include <thread>
12#include <fmt/chrono.h> 12#include <fmt/chrono.h>
13#include <fmt/format.h> 13#include <fmt/format.h>
14#include "common/file_util.h" 14#include "common/fs/file.h"
15#include "common/fs/fs.h"
16#include "common/fs/path_util.h"
15#include "common/math_util.h" 17#include "common/math_util.h"
16#include "common/settings.h" 18#include "common/settings.h"
17#include "core/perf_stats.h" 19#include "core/perf_stats.h"
@@ -38,12 +40,17 @@ PerfStats::~PerfStats() {
38 std::ostringstream stream; 40 std::ostringstream stream;
39 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, 41 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
40 std::ostream_iterator<double>(stream, "\n")); 42 std::ostream_iterator<double>(stream, "\n"));
41 const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir); 43
44 const auto path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir);
42 // %F Date format expanded is "%Y-%m-%d" 45 // %F Date format expanded is "%Y-%m-%d"
43 const std::string filename = 46 const auto filename = fmt::format("{:%F-%H-%M}_{:016X}.csv", *std::localtime(&t), title_id);
44 fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); 47 const auto filepath = path / filename;
45 Common::FS::IOFile file(filename, "w"); 48
46 file.WriteString(stream.str()); 49 if (Common::FS::CreateParentDir(filepath)) {
50 Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write,
51 Common::FS::FileType::TextFile);
52 void(file.WriteString(stream.str()));
53 }
47} 54}
48 55
49void PerfStats::BeginSystemFrame() { 56void PerfStats::BeginSystemFrame() {
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index d1e807dd4..a9596fe4d 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -11,7 +11,9 @@
11#include <fmt/ostream.h> 11#include <fmt/ostream.h>
12#include <nlohmann/json.hpp> 12#include <nlohmann/json.hpp>
13 13
14#include "common/file_util.h" 14#include "common/fs/file.h"
15#include "common/fs/fs.h"
16#include "common/fs/path_util.h"
15#include "common/hex_util.h" 17#include "common/hex_util.h"
16#include "common/scm_rev.h" 18#include "common/scm_rev.h"
17#include "common/settings.h" 19#include "common/settings.h"
@@ -26,10 +28,9 @@
26 28
27namespace { 29namespace {
28 30
29std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { 31std::filesystem::path GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
30 return fmt::format("{}{}/{:016X}_{}.json", 32 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / type /
31 Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id, 33 fmt::format("{:016X}_{}.json", title_id, timestamp);
32 timestamp);
33} 34}
34 35
35std::string GetTimestamp() { 36std::string GetTimestamp() {
@@ -39,14 +40,16 @@ std::string GetTimestamp() {
39 40
40using namespace nlohmann; 41using namespace nlohmann;
41 42
42void SaveToFile(json json, const std::string& filename) { 43void SaveToFile(json json, const std::filesystem::path& filename) {
43 if (!Common::FS::CreateFullPath(filename)) { 44 if (!Common::FS::CreateParentDirs(filename)) {
44 LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); 45 LOG_ERROR(Core, "Failed to create path for '{}' to save report!",
46 Common::FS::PathToUTF8String(filename));
45 return; 47 return;
46 } 48 }
47 49
48 std::ofstream file( 50 std::ofstream file;
49 Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault)); 51 Common::FS::OpenFileStream(file, filename, std::ios_base::out | std::ios_base::trunc);
52
50 file << std::setw(4) << json << std::endl; 53 file << std::setw(4) << json << std::endl;
51} 54}
52 55
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 6dcff5400..ad1a9ffb4 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -9,7 +9,9 @@
9 9
10#include "common/assert.h" 10#include "common/assert.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/file_util.h" 12#include "common/fs/file.h"
13#include "common/fs/fs.h"
14#include "common/fs/path_util.h"
13#include "common/logging/log.h" 15#include "common/logging/log.h"
14 16
15#include "common/settings.h" 17#include "common/settings.h"
@@ -72,31 +74,41 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
72 74
73u64 GetTelemetryId() { 75u64 GetTelemetryId() {
74 u64 telemetry_id{}; 76 u64 telemetry_id{};
75 const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + 77 const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
76 "telemetry_id"};
77 78
78 bool generate_new_id = !Common::FS::Exists(filename); 79 bool generate_new_id = !Common::FS::Exists(filename);
80
79 if (!generate_new_id) { 81 if (!generate_new_id) {
80 Common::FS::IOFile file(filename, "rb"); 82 Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read,
83 Common::FS::FileType::BinaryFile};
84
81 if (!file.IsOpen()) { 85 if (!file.IsOpen()) {
82 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 86 LOG_ERROR(Core, "failed to open telemetry_id: {}",
87 Common::FS::PathToUTF8String(filename));
83 return {}; 88 return {};
84 } 89 }
85 file.ReadBytes(&telemetry_id, sizeof(u64)); 90
86 if (telemetry_id == 0) { 91 if (!file.ReadObject(telemetry_id) || telemetry_id == 0) {
87 LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); 92 LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
88 generate_new_id = true; 93 generate_new_id = true;
89 } 94 }
90 } 95 }
91 96
92 if (generate_new_id) { 97 if (generate_new_id) {
93 Common::FS::IOFile file(filename, "wb"); 98 Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
99 Common::FS::FileType::BinaryFile};
100
94 if (!file.IsOpen()) { 101 if (!file.IsOpen()) {
95 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 102 LOG_ERROR(Core, "failed to open telemetry_id: {}",
103 Common::FS::PathToUTF8String(filename));
96 return {}; 104 return {};
97 } 105 }
106
98 telemetry_id = GenerateTelemetryId(); 107 telemetry_id = GenerateTelemetryId();
99 file.WriteBytes(&telemetry_id, sizeof(u64)); 108
109 if (!file.WriteObject(telemetry_id)) {
110 LOG_ERROR(Core, "Failed to write telemetry_id to file.");
111 }
100 } 112 }
101 113
102 return telemetry_id; 114 return telemetry_id;
@@ -104,15 +116,20 @@ u64 GetTelemetryId() {
104 116
105u64 RegenerateTelemetryId() { 117u64 RegenerateTelemetryId() {
106 const u64 new_telemetry_id{GenerateTelemetryId()}; 118 const u64 new_telemetry_id{GenerateTelemetryId()};
107 const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + 119 const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
108 "telemetry_id"}; 120
121 Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
122 Common::FS::FileType::BinaryFile};
109 123
110 Common::FS::IOFile file(filename, "wb");
111 if (!file.IsOpen()) { 124 if (!file.IsOpen()) {
112 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 125 LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename));
113 return {}; 126 return {};
114 } 127 }
115 file.WriteBytes(&new_telemetry_id, sizeof(u64)); 128
129 if (!file.WriteObject(new_telemetry_id)) {
130 LOG_ERROR(Core, "Failed to write telemetry_id to file.");
131 }
132
116 return new_telemetry_id; 133 return new_telemetry_id;
117} 134}
118 135
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp
index b35459152..e0c66fa2e 100644
--- a/src/tests/core/core_timing.cpp
+++ b/src/tests/core/core_timing.cpp
@@ -11,7 +11,6 @@
11#include <memory> 11#include <memory>
12#include <string> 12#include <string>
13 13
14#include "common/file_util.h"
15#include "core/core.h" 14#include "core/core.h"
16#include "core/core_timing.h" 15#include "core/core_timing.h"
17 16
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index dbcb751cb..0deb86517 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -7,9 +7,10 @@
7#include <fmt/format.h> 7#include <fmt/format.h>
8 8
9#include "common/assert.h" 9#include "common/assert.h"
10#include "common/common_paths.h"
11#include "common/common_types.h" 10#include "common/common_types.h"
12#include "common/file_util.h" 11#include "common/fs/file.h"
12#include "common/fs/fs.h"
13#include "common/fs/path_util.h"
13#include "common/logging/log.h" 14#include "common/logging/log.h"
14#include "common/scm_rev.h" 15#include "common/scm_rev.h"
15#include "common/settings.h" 16#include "common/settings.h"
@@ -26,11 +27,7 @@ using Tegra::Engines::ShaderType;
26using VideoCommon::Shader::BindlessSamplerMap; 27using VideoCommon::Shader::BindlessSamplerMap;
27using VideoCommon::Shader::BoundSamplerMap; 28using VideoCommon::Shader::BoundSamplerMap;
28using VideoCommon::Shader::KeyMap; 29using VideoCommon::Shader::KeyMap;
29
30namespace {
31
32using VideoCommon::Shader::SeparateSamplerKey; 30using VideoCommon::Shader::SeparateSamplerKey;
33
34using ShaderCacheVersionHash = std::array<u8, 64>; 31using ShaderCacheVersionHash = std::array<u8, 64>;
35 32
36struct ConstBufferKey { 33struct ConstBufferKey {
@@ -58,6 +55,8 @@ struct BindlessSamplerEntry {
58 Tegra::Engines::SamplerDescriptor sampler; 55 Tegra::Engines::SamplerDescriptor sampler;
59}; 56};
60 57
58namespace {
59
61constexpr u32 NativeVersion = 21; 60constexpr u32 NativeVersion = 21;
62 61
63ShaderCacheVersionHash GetShaderCacheVersionHash() { 62ShaderCacheVersionHash GetShaderCacheVersionHash() {
@@ -74,22 +73,20 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default;
74ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; 73ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default;
75 74
76bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { 75bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
77 if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { 76 if (!file.ReadObject(type)) {
78 return false; 77 return false;
79 } 78 }
80 u32 code_size; 79 u32 code_size;
81 u32 code_size_b; 80 u32 code_size_b;
82 if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) || 81 if (!file.ReadObject(code_size) || !file.ReadObject(code_size_b)) {
83 file.ReadBytes(&code_size_b, sizeof(u32)) != sizeof(u32)) {
84 return false; 82 return false;
85 } 83 }
86 code.resize(code_size); 84 code.resize(code_size);
87 code_b.resize(code_size_b); 85 code_b.resize(code_size_b);
88 86 if (file.Read(code) != code_size) {
89 if (file.ReadArray(code.data(), code_size) != code_size) {
90 return false; 87 return false;
91 } 88 }
92 if (HasProgramA() && file.ReadArray(code_b.data(), code_size_b) != code_size_b) { 89 if (HasProgramA() && file.Read(code_b) != code_size_b) {
93 return false; 90 return false;
94 } 91 }
95 92
@@ -99,13 +96,12 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
99 u32 num_bound_samplers; 96 u32 num_bound_samplers;
100 u32 num_separate_samplers; 97 u32 num_separate_samplers;
101 u32 num_bindless_samplers; 98 u32 num_bindless_samplers;
102 if (file.ReadArray(&unique_identifier, 1) != 1 || file.ReadArray(&bound_buffer, 1) != 1 || 99 if (!file.ReadObject(unique_identifier) || !file.ReadObject(bound_buffer) ||
103 file.ReadArray(&is_texture_handler_size_known, 1) != 1 || 100 !file.ReadObject(is_texture_handler_size_known) ||
104 file.ReadArray(&texture_handler_size_value, 1) != 1 || 101 !file.ReadObject(texture_handler_size_value) || !file.ReadObject(graphics_info) ||
105 file.ReadArray(&graphics_info, 1) != 1 || file.ReadArray(&compute_info, 1) != 1 || 102 !file.ReadObject(compute_info) || !file.ReadObject(num_keys) ||
106 file.ReadArray(&num_keys, 1) != 1 || file.ReadArray(&num_bound_samplers, 1) != 1 || 103 !file.ReadObject(num_bound_samplers) || !file.ReadObject(num_separate_samplers) ||
107 file.ReadArray(&num_separate_samplers, 1) != 1 || 104 !file.ReadObject(num_bindless_samplers)) {
108 file.ReadArray(&num_bindless_samplers, 1) != 1) {
109 return false; 105 return false;
110 } 106 }
111 if (is_texture_handler_size_known) { 107 if (is_texture_handler_size_known) {
@@ -116,13 +112,10 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
116 std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers); 112 std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers);
117 std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers); 113 std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers);
118 std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers); 114 std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers);
119 if (file.ReadArray(flat_keys.data(), flat_keys.size()) != flat_keys.size() || 115 if (file.Read(flat_keys) != flat_keys.size() ||
120 file.ReadArray(flat_bound_samplers.data(), flat_bound_samplers.size()) != 116 file.Read(flat_bound_samplers) != flat_bound_samplers.size() ||
121 flat_bound_samplers.size() || 117 file.Read(flat_separate_samplers) != flat_separate_samplers.size() ||
122 file.ReadArray(flat_separate_samplers.data(), flat_separate_samplers.size()) != 118 file.Read(flat_bindless_samplers) != flat_bindless_samplers.size()) {
123 flat_separate_samplers.size() ||
124 file.ReadArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) !=
125 flat_bindless_samplers.size()) {
126 return false; 119 return false;
127 } 120 }
128 for (const auto& entry : flat_keys) { 121 for (const auto& entry : flat_keys) {
@@ -145,26 +138,25 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
145} 138}
146 139
147bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { 140bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
148 if (file.WriteObject(static_cast<u32>(type)) != 1 || 141 if (!file.WriteObject(static_cast<u32>(type)) ||
149 file.WriteObject(static_cast<u32>(code.size())) != 1 || 142 !file.WriteObject(static_cast<u32>(code.size())) ||
150 file.WriteObject(static_cast<u32>(code_b.size())) != 1) { 143 !file.WriteObject(static_cast<u32>(code_b.size()))) {
151 return false; 144 return false;
152 } 145 }
153 if (file.WriteArray(code.data(), code.size()) != code.size()) { 146 if (file.Write(code) != code.size()) {
154 return false; 147 return false;
155 } 148 }
156 if (HasProgramA() && file.WriteArray(code_b.data(), code_b.size()) != code_b.size()) { 149 if (HasProgramA() && file.Write(code_b) != code_b.size()) {
157 return false; 150 return false;
158 } 151 }
159 152
160 if (file.WriteObject(unique_identifier) != 1 || file.WriteObject(bound_buffer) != 1 || 153 if (!file.WriteObject(unique_identifier) || !file.WriteObject(bound_buffer) ||
161 file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) != 1 || 154 !file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) ||
162 file.WriteObject(texture_handler_size.value_or(0)) != 1 || 155 !file.WriteObject(texture_handler_size.value_or(0)) || !file.WriteObject(graphics_info) ||
163 file.WriteObject(graphics_info) != 1 || file.WriteObject(compute_info) != 1 || 156 !file.WriteObject(compute_info) || !file.WriteObject(static_cast<u32>(keys.size())) ||
164 file.WriteObject(static_cast<u32>(keys.size())) != 1 || 157 !file.WriteObject(static_cast<u32>(bound_samplers.size())) ||
165 file.WriteObject(static_cast<u32>(bound_samplers.size())) != 1 || 158 !file.WriteObject(static_cast<u32>(separate_samplers.size())) ||
166 file.WriteObject(static_cast<u32>(separate_samplers.size())) != 1 || 159 !file.WriteObject(static_cast<u32>(bindless_samplers.size()))) {
167 file.WriteObject(static_cast<u32>(bindless_samplers.size())) != 1) {
168 return false; 160 return false;
169 } 161 }
170 162
@@ -197,13 +189,10 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
197 BindlessSamplerEntry{address.first, address.second, sampler}); 189 BindlessSamplerEntry{address.first, address.second, sampler});
198 } 190 }
199 191
200 return file.WriteArray(flat_keys.data(), flat_keys.size()) == flat_keys.size() && 192 return file.Write(flat_keys) == flat_keys.size() &&
201 file.WriteArray(flat_bound_samplers.data(), flat_bound_samplers.size()) == 193 file.Write(flat_bound_samplers) == flat_bound_samplers.size() &&
202 flat_bound_samplers.size() && 194 file.Write(flat_separate_samplers) == flat_separate_samplers.size() &&
203 file.WriteArray(flat_separate_samplers.data(), flat_separate_samplers.size()) == 195 file.Write(flat_bindless_samplers) == flat_bindless_samplers.size();
204 flat_separate_samplers.size() &&
205 file.WriteArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) ==
206 flat_bindless_samplers.size();
207} 196}
208 197
209ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default; 198ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default;
@@ -221,7 +210,8 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
221 return std::nullopt; 210 return std::nullopt;
222 } 211 }
223 212
224 Common::FS::IOFile file(GetTransferablePath(), "rb"); 213 Common::FS::IOFile file{GetTransferablePath(), Common::FS::FileAccessMode::Read,
214 Common::FS::FileType::BinaryFile};
225 if (!file.IsOpen()) { 215 if (!file.IsOpen()) {
226 LOG_INFO(Render_OpenGL, "No transferable shader cache found"); 216 LOG_INFO(Render_OpenGL, "No transferable shader cache found");
227 is_usable = true; 217 is_usable = true;
@@ -229,7 +219,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
229 } 219 }
230 220
231 u32 version{}; 221 u32 version{};
232 if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { 222 if (!file.ReadObject(version)) {
233 LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); 223 LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it");
234 return std::nullopt; 224 return std::nullopt;
235 } 225 }
@@ -249,7 +239,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
249 239
250 // Version is valid, load the shaders 240 // Version is valid, load the shaders
251 std::vector<ShaderDiskCacheEntry> entries; 241 std::vector<ShaderDiskCacheEntry> entries;
252 while (file.Tell() < file.GetSize()) { 242 while (static_cast<u64>(file.Tell()) < file.GetSize()) {
253 ShaderDiskCacheEntry& entry = entries.emplace_back(); 243 ShaderDiskCacheEntry& entry = entries.emplace_back();
254 if (!entry.Load(file)) { 244 if (!entry.Load(file)) {
255 LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); 245 LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping");
@@ -266,7 +256,8 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
266 return {}; 256 return {};
267 } 257 }
268 258
269 Common::FS::IOFile file(GetPrecompiledPath(), "rb"); 259 Common::FS::IOFile file{GetPrecompiledPath(), Common::FS::FileAccessMode::Read,
260 Common::FS::FileType::BinaryFile};
270 if (!file.IsOpen()) { 261 if (!file.IsOpen()) {
271 LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); 262 LOG_INFO(Render_OpenGL, "No precompiled shader cache found");
272 return {}; 263 return {};
@@ -286,7 +277,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
286 Common::FS::IOFile& file) { 277 Common::FS::IOFile& file) {
287 // Read compressed file from disk and decompress to virtual precompiled cache file 278 // Read compressed file from disk and decompress to virtual precompiled cache file
288 std::vector<u8> compressed(file.GetSize()); 279 std::vector<u8> compressed(file.GetSize());
289 file.ReadBytes(compressed.data(), compressed.size()); 280 if (file.Read(compressed) != file.GetSize()) {
281 return std::nullopt;
282 }
290 const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed); 283 const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed);
291 SaveArrayToPrecompiled(decompressed.data(), decompressed.size()); 284 SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
292 precompiled_cache_virtual_file_offset = 0; 285 precompiled_cache_virtual_file_offset = 0;
@@ -321,9 +314,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
321} 314}
322 315
323void ShaderDiskCacheOpenGL::InvalidateTransferable() { 316void ShaderDiskCacheOpenGL::InvalidateTransferable() {
324 if (!Common::FS::Delete(GetTransferablePath())) { 317 if (!Common::FS::RemoveFile(GetTransferablePath())) {
325 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", 318 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
326 GetTransferablePath()); 319 Common::FS::PathToUTF8String(GetTransferablePath()));
327 } 320 }
328 InvalidatePrecompiled(); 321 InvalidatePrecompiled();
329} 322}
@@ -332,8 +325,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
332 // Clear virtaul precompiled cache file 325 // Clear virtaul precompiled cache file
333 precompiled_cache_virtual_file.Resize(0); 326 precompiled_cache_virtual_file.Resize(0);
334 327
335 if (!Common::FS::Delete(GetPrecompiledPath())) { 328 if (!Common::FS::RemoveFile(GetPrecompiledPath())) {
336 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); 329 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}",
330 Common::FS::PathToUTF8String(GetPrecompiledPath()));
337 } 331 }
338} 332}
339 333
@@ -398,16 +392,18 @@ Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
398 const auto transferable_path{GetTransferablePath()}; 392 const auto transferable_path{GetTransferablePath()};
399 const bool existed = Common::FS::Exists(transferable_path); 393 const bool existed = Common::FS::Exists(transferable_path);
400 394
401 Common::FS::IOFile file(transferable_path, "ab"); 395 Common::FS::IOFile file{transferable_path, Common::FS::FileAccessMode::Append,
396 Common::FS::FileType::BinaryFile};
402 if (!file.IsOpen()) { 397 if (!file.IsOpen()) {
403 LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); 398 LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}",
399 Common::FS::PathToUTF8String(transferable_path));
404 return {}; 400 return {};
405 } 401 }
406 if (!existed || file.GetSize() == 0) { 402 if (!existed || file.GetSize() == 0) {
407 // If the file didn't exist, write its version 403 // If the file didn't exist, write its version
408 if (file.WriteObject(NativeVersion) != 1) { 404 if (!file.WriteObject(NativeVersion)) {
409 LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}", 405 LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}",
410 transferable_path); 406 Common::FS::PathToUTF8String(transferable_path));
411 return {}; 407 return {};
412 } 408 }
413 } 409 }
@@ -429,51 +425,54 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
429 const std::vector<u8> compressed = 425 const std::vector<u8> compressed =
430 Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); 426 Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
431 427
432 const auto precompiled_path{GetPrecompiledPath()}; 428 const auto precompiled_path = GetPrecompiledPath();
433 Common::FS::IOFile file(precompiled_path, "wb"); 429 Common::FS::IOFile file{precompiled_path, Common::FS::FileAccessMode::Write,
430 Common::FS::FileType::BinaryFile};
434 431
435 if (!file.IsOpen()) { 432 if (!file.IsOpen()) {
436 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); 433 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}",
434 Common::FS::PathToUTF8String(precompiled_path));
437 return; 435 return;
438 } 436 }
439 if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) { 437 if (file.Write(compressed) != compressed.size()) {
440 LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", 438 LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
441 precompiled_path); 439 Common::FS::PathToUTF8String(precompiled_path));
442 } 440 }
443} 441}
444 442
445bool ShaderDiskCacheOpenGL::EnsureDirectories() const { 443bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
446 const auto CreateDir = [](const std::string& dir) { 444 const auto CreateDir = [](const std::filesystem::path& dir) {
447 if (!Common::FS::CreateDir(dir)) { 445 if (!Common::FS::CreateDir(dir)) {
448 LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); 446 LOG_ERROR(Render_OpenGL, "Failed to create directory={}",
447 Common::FS::PathToUTF8String(dir));
449 return false; 448 return false;
450 } 449 }
451 return true; 450 return true;
452 }; 451 };
453 452
454 return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) && 453 return CreateDir(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir)) &&
455 CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && 454 CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) &&
456 CreateDir(GetPrecompiledDir()); 455 CreateDir(GetPrecompiledDir());
457} 456}
458 457
459std::string ShaderDiskCacheOpenGL::GetTransferablePath() const { 458std::filesystem::path ShaderDiskCacheOpenGL::GetTransferablePath() const {
460 return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); 459 return GetTransferableDir() / fmt::format("{}.bin", GetTitleID());
461} 460}
462 461
463std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { 462std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledPath() const {
464 return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); 463 return GetPrecompiledDir() / fmt::format("{}.bin", GetTitleID());
465} 464}
466 465
467std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { 466std::filesystem::path ShaderDiskCacheOpenGL::GetTransferableDir() const {
468 return GetBaseDir() + DIR_SEP "transferable"; 467 return GetBaseDir() / "transferable";
469} 468}
470 469
471std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const { 470std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledDir() const {
472 return GetBaseDir() + DIR_SEP "precompiled"; 471 return GetBaseDir() / "precompiled";
473} 472}
474 473
475std::string ShaderDiskCacheOpenGL::GetBaseDir() const { 474std::filesystem::path ShaderDiskCacheOpenGL::GetBaseDir() const {
476 return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl"; 475 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "opengl";
477} 476}
478 477
479std::string ShaderDiskCacheOpenGL::GetTitleID() const { 478std::string ShaderDiskCacheOpenGL::GetTitleID() const {
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index aef841c1d..f8bc23868 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <filesystem>
7#include <optional> 8#include <optional>
8#include <string> 9#include <string>
9#include <tuple> 10#include <tuple>
@@ -108,19 +109,19 @@ private:
108 bool EnsureDirectories() const; 109 bool EnsureDirectories() const;
109 110
110 /// Gets current game's transferable file path 111 /// Gets current game's transferable file path
111 std::string GetTransferablePath() const; 112 std::filesystem::path GetTransferablePath() const;
112 113
113 /// Gets current game's precompiled file path 114 /// Gets current game's precompiled file path
114 std::string GetPrecompiledPath() const; 115 std::filesystem::path GetPrecompiledPath() const;
115 116
116 /// Get user's transferable directory path 117 /// Get user's transferable directory path
117 std::string GetTransferableDir() const; 118 std::filesystem::path GetTransferableDir() const;
118 119
119 /// Get user's precompiled directory path 120 /// Get user's precompiled directory path
120 std::string GetPrecompiledDir() const; 121 std::filesystem::path GetPrecompiledDir() const;
121 122
122 /// Get user's shader directory path 123 /// Get user's shader directory path
123 std::string GetBaseDir() const; 124 std::filesystem::path GetBaseDir() const;
124 125
125 /// Get current game's title id 126 /// Get current game's title id
126 std::string GetTitleID() const; 127 std::string GetTitleID() const;
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
index 7a9d00d4f..f0ee76519 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
@@ -5,6 +5,7 @@
5#ifdef HAS_NSIGHT_AFTERMATH 5#ifdef HAS_NSIGHT_AFTERMATH
6 6
7#include <mutex> 7#include <mutex>
8#include <span>
8#include <string> 9#include <string>
9#include <string_view> 10#include <string_view>
10#include <utility> 11#include <utility>
@@ -12,9 +13,10 @@
12 13
13#include <fmt/format.h> 14#include <fmt/format.h>
14 15
15#include "common/common_paths.h"
16#include "common/common_types.h" 16#include "common/common_types.h"
17#include "common/file_util.h" 17#include "common/fs/file.h"
18#include "common/fs/fs.h"
19#include "common/fs/path_util.h"
18#include "common/logging/log.h" 20#include "common/logging/log.h"
19#include "common/scope_exit.h" 21#include "common/scope_exit.h"
20#include "video_core/vulkan_common/nsight_aftermath_tracker.h" 22#include "video_core/vulkan_common/nsight_aftermath_tracker.h"
@@ -46,9 +48,9 @@ NsightAftermathTracker::NsightAftermathTracker() {
46 LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers"); 48 LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers");
47 return; 49 return;
48 } 50 }
49 dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash"; 51 dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash";
50 52
51 void(Common::FS::DeleteDirRecursively(dump_dir)); 53 void(Common::FS::RemoveDirRecursively(dump_dir));
52 if (!Common::FS::CreateDir(dump_dir)) { 54 if (!Common::FS::CreateDir(dump_dir)) {
53 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); 55 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
54 return; 56 return;
@@ -60,7 +62,8 @@ NsightAftermathTracker::NsightAftermathTracker() {
60 LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); 62 LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
61 return; 63 return;
62 } 64 }
63 LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir); 65 LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"",
66 Common::FS::PathToUTF8String(dump_dir));
64 initialized = true; 67 initialized = true;
65} 68}
66 69
@@ -89,12 +92,15 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
89 return; 92 return;
90 } 93 }
91 94
92 Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); 95 const auto shader_file = dump_dir / fmt::format("source_{:016x}.spv", hash.hash);
96
97 Common::FS::IOFile file{shader_file, Common::FS::FileAccessMode::Write,
98 Common::FS::FileType::BinaryFile};
93 if (!file.IsOpen()) { 99 if (!file.IsOpen()) {
94 LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); 100 LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
95 return; 101 return;
96 } 102 }
97 if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) { 103 if (file.Write(spirv) != spirv.size()) {
98 LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash); 104 LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash);
99 return; 105 return;
100 } 106 }
@@ -129,22 +135,24 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
129 return; 135 return;
130 } 136 }
131 137
132 const std::string base_name = [this] { 138 std::filesystem::path base_name = [this] {
133 const int id = dump_id++; 139 const int id = dump_id++;
134 if (id == 0) { 140 if (id == 0) {
135 return fmt::format("{}/crash.nv-gpudmp", dump_dir); 141 return dump_dir / "crash.nv-gpudmp";
136 } else { 142 } else {
137 return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id); 143 return dump_dir / fmt::format("crash_{}.nv-gpudmp", id);
138 } 144 }
139 }(); 145 }();
140 146
141 std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); 147 std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
142 if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { 148 if (Common::FS::WriteStringToFile(base_name, Common::FS::FileType::BinaryFile, dump_view) !=
149 gpu_crash_dump_size) {
143 LOG_ERROR(Render_Vulkan, "Failed to write dump file"); 150 LOG_ERROR(Render_Vulkan, "Failed to write dump file");
144 return; 151 return;
145 } 152 }
146 const std::string_view json_view(json.data(), json.size()); 153 const std::string_view json_view(json.data(), json.size());
147 if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { 154 if (Common::FS::WriteStringToFile(base_name.concat(".json"), Common::FS::FileType::TextFile,
155 json_view) != json.size()) {
148 LOG_ERROR(Render_Vulkan, "Failed to write JSON"); 156 LOG_ERROR(Render_Vulkan, "Failed to write JSON");
149 return; 157 return;
150 } 158 }
@@ -161,16 +169,17 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_
161 return; 169 return;
162 } 170 }
163 171
164 const std::string path = 172 const auto path =
165 fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]); 173 dump_dir / fmt::format("shader_{:016x}{:016x}.nvdbg", identifier.id[0], identifier.id[1]);
166 Common::FS::IOFile file(path, "wb"); 174 Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
175 Common::FS::FileType::BinaryFile};
167 if (!file.IsOpen()) { 176 if (!file.IsOpen()) {
168 LOG_ERROR(Render_Vulkan, "Failed to create file {}", path); 177 LOG_ERROR(Render_Vulkan, "Failed to create file {}", Common::FS::PathToUTF8String(path));
169 return; 178 return;
170 } 179 }
171 if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) != 180 if (file.WriteSpan(std::span(static_cast<const u8*>(shader_debug_info),
172 shader_debug_info_size) { 181 shader_debug_info_size)) != shader_debug_info_size) {
173 LOG_ERROR(Render_Vulkan, "Failed to write file {}", path); 182 LOG_ERROR(Render_Vulkan, "Failed to write file {}", Common::FS::PathToUTF8String(path));
174 return; 183 return;
175 } 184 }
176} 185}
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.h b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
index 1ce8d4e8e..4fe2b14d9 100644
--- a/src/video_core/vulkan_common/nsight_aftermath_tracker.h
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <filesystem>
7#include <mutex> 8#include <mutex>
8#include <string> 9#include <string>
9#include <vector> 10#include <vector>
@@ -54,7 +55,7 @@ private:
54 55
55 mutable std::mutex mutex; 56 mutable std::mutex mutex;
56 57
57 std::string dump_dir; 58 std::filesystem::path dump_dir;
58 int dump_id = 0; 59 int dump_id = 0;
59 60
60 bool initialized = false; 61 bool initialized = false;
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
index 557871d81..22833fa56 100644
--- a/src/video_core/vulkan_common/vulkan_library.cpp
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -6,7 +6,7 @@
6#include <string> 6#include <string>
7 7
8#include "common/dynamic_library.h" 8#include "common/dynamic_library.h"
9#include "common/file_util.h" 9#include "common/fs/path_util.h"
10#include "video_core/vulkan_common/vulkan_library.h" 10#include "video_core/vulkan_common/vulkan_library.h"
11 11
12namespace Vulkan { 12namespace Vulkan {
@@ -18,9 +18,9 @@ Common::DynamicLibrary OpenLibrary() {
18 char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); 18 char* const libvulkan_env = std::getenv("LIBVULKAN_PATH");
19 if (!libvulkan_env || !library.Open(libvulkan_env)) { 19 if (!libvulkan_env || !library.Open(libvulkan_env)) {
20 // Use the libvulkan.dylib from the application bundle. 20 // Use the libvulkan.dylib from the application bundle.
21 const std::string filename = 21 const auto filename =
22 Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; 22 Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib";
23 void(library.Open(filename.c_str())); 23 void(library.Open(Common::FS::PathToUTF8String(filename).c_str()));
24 } 24 }
25#else 25#else
26 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); 26 std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 0a4c48b3d..62fd1141c 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -10,7 +10,7 @@
10#include <QScrollArea> 10#include <QScrollArea>
11#include <QStandardItemModel> 11#include <QStandardItemModel>
12#include <QVBoxLayout> 12#include <QVBoxLayout>
13#include "common/file_util.h" 13#include "common/fs/path_util.h"
14#include "common/string_util.h" 14#include "common/string_util.h"
15#include "core/constants.h" 15#include "core/constants.h"
16#include "core/hle/lock.h" 16#include "core/hle/lock.h"
@@ -26,9 +26,10 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
26} 26}
27 27
28QString GetImagePath(Common::UUID uuid) { 28QString GetImagePath(Common::UUID uuid) {
29 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 29 const auto path =
30 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 30 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
31 return QString::fromStdString(path); 31 fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch());
32 return QString::fromStdString(Common::FS::PathToUTF8String(path));
32} 33}
33 34
34QPixmap GetIcon(Common::UUID uuid) { 35QPixmap GetIcon(Common::UUID uuid) {
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
index 93e3a4f6f..34d3feb55 100644
--- a/src/yuzu/applets/web_browser.cpp
+++ b/src/yuzu/applets/web_browser.cpp
@@ -12,7 +12,7 @@
12#include <QWebEngineUrlScheme> 12#include <QWebEngineUrlScheme>
13#endif 13#endif
14 14
15#include "common/file_util.h" 15#include "common/fs/path_util.h"
16#include "core/core.h" 16#include "core/core.h"
17#include "core/frontend/input_interpreter.h" 17#include "core/frontend/input_interpreter.h"
18#include "input_common/keyboard.h" 18#include "input_common/keyboard.h"
@@ -322,21 +322,25 @@ void QtNXWebEngineView::LoadExtractedFonts() {
322 QWebEngineScript nx_font_css; 322 QWebEngineScript nx_font_css;
323 QWebEngineScript load_nx_font; 323 QWebEngineScript load_nx_font;
324 324
325 const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( 325 auto fonts_dir_str = Common::FS::PathToUTF8String(
326 fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); 326 Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/");
327
328 std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/');
329
330 const auto fonts_dir = QString::fromStdString(fonts_dir_str);
327 331
328 nx_font_css.setName(QStringLiteral("nx_font_css.js")); 332 nx_font_css.setName(QStringLiteral("nx_font_css.js"));
329 load_nx_font.setName(QStringLiteral("load_nx_font.js")); 333 load_nx_font.setName(QStringLiteral("load_nx_font.js"));
330 334
331 nx_font_css.setSourceCode( 335 nx_font_css.setSourceCode(
332 QString::fromStdString(NX_FONT_CSS) 336 QString::fromStdString(NX_FONT_CSS)
333 .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) 337 .arg(fonts_dir + QStringLiteral("FontStandard.ttf"))
334 .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) 338 .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf"))
335 .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) 339 .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf"))
336 .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) 340 .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf"))
337 .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) 341 .arg(fonts_dir + QStringLiteral("FontKorean.ttf"))
338 .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) 342 .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf"))
339 .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); 343 .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf")));
340 load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); 344 load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
341 345
342 nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); 346 nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 21d1dc174..eb58bfa5b 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -5,8 +5,8 @@
5#include <array> 5#include <array>
6#include <QKeySequence> 6#include <QKeySequence>
7#include <QSettings> 7#include <QSettings>
8#include "common/common_paths.h" 8#include "common/fs/fs.h"
9#include "common/file_util.h" 9#include "common/fs/path_util.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/hle/service/acc/profile_manager.h" 11#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/hid/controllers/npad.h" 12#include "core/hle/service/hid/controllers/npad.h"
@@ -243,27 +243,27 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
243// clang-format on 243// clang-format on
244 244
245void Config::Initialize(const std::string& config_name) { 245void Config::Initialize(const std::string& config_name) {
246 const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
247 const auto config_file = fmt::format("{}.ini", config_name);
248
246 switch (type) { 249 switch (type) {
247 case ConfigType::GlobalConfig: 250 case ConfigType::GlobalConfig:
248 qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), 251 qt_config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
249 config_name); 252 void(FS::CreateParentDir(qt_config_loc));
250 FS::CreateFullPath(qt_config_loc);
251 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), 253 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
252 QSettings::IniFormat); 254 QSettings::IniFormat);
253 Reload(); 255 Reload();
254 break; 256 break;
255 case ConfigType::PerGameConfig: 257 case ConfigType::PerGameConfig:
256 qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", 258 qt_config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / config_file);
257 FS::GetUserPath(FS::UserPath::ConfigDir), config_name); 259 void(FS::CreateParentDir(qt_config_loc));
258 FS::CreateFullPath(qt_config_loc);
259 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), 260 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
260 QSettings::IniFormat); 261 QSettings::IniFormat);
261 Reload(); 262 Reload();
262 break; 263 break;
263 case ConfigType::InputProfile: 264 case ConfigType::InputProfile:
264 qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", 265 qt_config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
265 FS::GetUserPath(FS::UserPath::ConfigDir), config_name); 266 void(FS::CreateParentDir(qt_config_loc));
266 FS::CreateFullPath(qt_config_loc);
267 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), 267 qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
268 QSettings::IniFormat); 268 QSettings::IniFormat);
269 break; 269 break;
@@ -598,30 +598,34 @@ void Config::ReadDataStorageValues() {
598 qt_config->beginGroup(QStringLiteral("Data Storage")); 598 qt_config->beginGroup(QStringLiteral("Data Storage"));
599 599
600 Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); 600 Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
601 FS::GetUserPath(FS::UserPath::NANDDir, 601 FS::SetYuzuPath(
602 qt_config 602 FS::YuzuPath::NANDDir,
603 ->value(QStringLiteral("nand_directory"), 603 qt_config
604 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))) 604 ->value(QStringLiteral("nand_directory"),
605 .toString() 605 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)))
606 .toStdString()); 606 .toString()
607 FS::GetUserPath(FS::UserPath::SDMCDir, 607 .toStdString());
608 qt_config 608 FS::SetYuzuPath(
609 ->value(QStringLiteral("sdmc_directory"), 609 FS::YuzuPath::SDMCDir,
610 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))) 610 qt_config
611 .toString() 611 ->value(QStringLiteral("sdmc_directory"),
612 .toStdString()); 612 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)))
613 FS::GetUserPath(FS::UserPath::LoadDir, 613 .toString()
614 qt_config 614 .toStdString());
615 ->value(QStringLiteral("load_directory"), 615 FS::SetYuzuPath(
616 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))) 616 FS::YuzuPath::LoadDir,
617 .toString() 617 qt_config
618 .toStdString()); 618 ->value(QStringLiteral("load_directory"),
619 FS::GetUserPath(FS::UserPath::DumpDir, 619 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)))
620 qt_config 620 .toString()
621 ->value(QStringLiteral("dump_directory"), 621 .toStdString());
622 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) 622 FS::SetYuzuPath(
623 .toString() 623 FS::YuzuPath::DumpDir,
624 .toStdString()); 624 qt_config
625 ->value(QStringLiteral("dump_directory"),
626 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)))
627 .toString()
628 .toStdString());
625 Settings::values.gamecard_inserted = 629 Settings::values.gamecard_inserted =
626 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); 630 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
627 Settings::values.gamecard_current_game = 631 Settings::values.gamecard_current_game =
@@ -817,11 +821,11 @@ void Config::ReadScreenshotValues() {
817 821
818 UISettings::values.enable_screenshot_save_as = 822 UISettings::values.enable_screenshot_save_as =
819 ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); 823 ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
820 FS::GetUserPath( 824 FS::SetYuzuPath(
821 FS::UserPath::ScreenshotsDir, 825 FS::YuzuPath::ScreenshotsDir,
822 qt_config 826 qt_config
823 ->value(QStringLiteral("screenshot_path"), 827 ->value(QStringLiteral("screenshot_path"),
824 QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))) 828 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)))
825 .toString() 829 .toString()
826 .toStdString()); 830 .toStdString());
827 831
@@ -1220,17 +1224,17 @@ void Config::SaveDataStorageValues() {
1220 1224
1221 WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); 1225 WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
1222 WriteSetting(QStringLiteral("nand_directory"), 1226 WriteSetting(QStringLiteral("nand_directory"),
1223 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)), 1227 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)),
1224 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))); 1228 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
1225 WriteSetting(QStringLiteral("sdmc_directory"), 1229 WriteSetting(QStringLiteral("sdmc_directory"),
1226 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)), 1230 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)),
1227 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))); 1231 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
1228 WriteSetting(QStringLiteral("load_directory"), 1232 WriteSetting(QStringLiteral("load_directory"),
1229 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)), 1233 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)),
1230 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))); 1234 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
1231 WriteSetting(QStringLiteral("dump_directory"), 1235 WriteSetting(QStringLiteral("dump_directory"),
1232 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), 1236 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)),
1233 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); 1237 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
1234 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); 1238 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
1235 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, 1239 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
1236 false); 1240 false);
@@ -1397,7 +1401,7 @@ void Config::SaveScreenshotValues() {
1397 WriteSetting(QStringLiteral("enable_screenshot_save_as"), 1401 WriteSetting(QStringLiteral("enable_screenshot_save_as"),
1398 UISettings::values.enable_screenshot_save_as); 1402 UISettings::values.enable_screenshot_save_as);
1399 WriteSetting(QStringLiteral("screenshot_path"), 1403 WriteSetting(QStringLiteral("screenshot_path"),
1400 QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))); 1404 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
1401 1405
1402 qt_config->endGroup(); 1406 qt_config->endGroup();
1403} 1407}
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 6730eb356..b207e07cb 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -4,7 +4,7 @@
4 4
5#include <QDesktopServices> 5#include <QDesktopServices>
6#include <QUrl> 6#include <QUrl>
7#include "common/file_util.h" 7#include "common/fs/path_util.h"
8#include "common/logging/backend.h" 8#include "common/logging/backend.h"
9#include "common/logging/filter.h" 9#include "common/logging/filter.h"
10#include "common/settings.h" 10#include "common/settings.h"
@@ -20,7 +20,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
20 20
21 connect(ui->open_log_button, &QPushButton::clicked, []() { 21 connect(ui->open_log_button, &QPushButton::clicked, []() {
22 const auto path = 22 const auto path =
23 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir)); 23 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
24 QDesktopServices::openUrl(QUrl::fromLocalFile(path)); 24 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
25 }); 25 });
26} 26}
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index 006eda4b0..d223c40ea 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -4,8 +4,8 @@
4 4
5#include <QFileDialog> 5#include <QFileDialog>
6#include <QMessageBox> 6#include <QMessageBox>
7#include "common/common_paths.h" 7#include "common/fs/fs.h"
8#include "common/file_util.h" 8#include "common/fs/path_util.h"
9#include "common/settings.h" 9#include "common/settings.h"
10#include "ui_configure_filesystem.h" 10#include "ui_configure_filesystem.h"
11#include "yuzu/configuration/configure_filesystem.h" 11#include "yuzu/configuration/configure_filesystem.h"
@@ -40,14 +40,14 @@ ConfigureFilesystem::~ConfigureFilesystem() = default;
40 40
41void ConfigureFilesystem::setConfiguration() { 41void ConfigureFilesystem::setConfiguration() {
42 ui->nand_directory_edit->setText( 42 ui->nand_directory_edit->setText(
43 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir))); 43 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir)));
44 ui->sdmc_directory_edit->setText( 44 ui->sdmc_directory_edit->setText(
45 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir))); 45 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::SDMCDir)));
46 ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); 46 ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
47 ui->dump_path_edit->setText( 47 ui->dump_path_edit->setText(
48 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); 48 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir)));
49 ui->load_path_edit->setText( 49 ui->load_path_edit->setText(
50 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); 50 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir)));
51 51
52 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); 52 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
53 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); 53 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
@@ -60,13 +60,13 @@ void ConfigureFilesystem::setConfiguration() {
60} 60}
61 61
62void ConfigureFilesystem::applyConfiguration() { 62void ConfigureFilesystem::applyConfiguration() {
63 Common::FS::GetUserPath(Common::FS::UserPath::NANDDir, 63 Common::FS::SetYuzuPath(Common::FS::YuzuPath::NANDDir,
64 ui->nand_directory_edit->text().toStdString()); 64 ui->nand_directory_edit->text().toStdString());
65 Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir, 65 Common::FS::SetYuzuPath(Common::FS::YuzuPath::SDMCDir,
66 ui->sdmc_directory_edit->text().toStdString()); 66 ui->sdmc_directory_edit->text().toStdString());
67 Common::FS::GetUserPath(Common::FS::UserPath::DumpDir, 67 Common::FS::SetYuzuPath(Common::FS::YuzuPath::DumpDir,
68 ui->dump_path_edit->text().toStdString()); 68 ui->dump_path_edit->text().toStdString());
69 Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, 69 Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir,
70 ui->load_path_edit->text().toStdString()); 70 ui->load_path_edit->text().toStdString());
71 71
72 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); 72 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
@@ -104,25 +104,26 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
104 QStringLiteral("NX Gamecard;*.xci")); 104 QStringLiteral("NX Gamecard;*.xci"));
105 } else { 105 } else {
106 str = QFileDialog::getExistingDirectory(this, caption, edit->text()); 106 str = QFileDialog::getExistingDirectory(this, caption, edit->text());
107 if (!str.isNull() && str.back() != QDir::separator()) {
108 str.append(QDir::separator());
109 }
110 } 107 }
111 108
112 if (str.isEmpty()) 109 if (str.isNull() || str.isEmpty()) {
113 return; 110 return;
111 }
112
113 if (str.back() != QChar::fromLatin1('/')) {
114 str.append(QChar::fromLatin1('/'));
115 }
114 116
115 edit->setText(str); 117 edit->setText(str);
116} 118}
117 119
118void ConfigureFilesystem::ResetMetadata() { 120void ConfigureFilesystem::ResetMetadata() {
119 if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 121 if (!Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
120 "game_list")) { 122 "game_list/")) {
121 QMessageBox::information(this, tr("Reset Metadata Cache"), 123 QMessageBox::information(this, tr("Reset Metadata Cache"),
122 tr("The metadata cache is already empty.")); 124 tr("The metadata cache is already empty."));
123 } else if (Common::FS::DeleteDirRecursively( 125 } else if (Common::FS::RemoveDirRecursively(
124 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 126 Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list")) {
125 "game_list")) {
126 QMessageBox::information(this, tr("Reset Metadata Cache"), 127 QMessageBox::information(this, tr("Reset Metadata Cache"),
127 tr("The operation completed successfully.")); 128 tr("The operation completed successfully."));
128 UISettings::values.is_game_list_reload_pending.exchange(true); 129 UISettings::values.is_game_list_reload_pending.exchange(true);
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index f550567e2..3e13bd438 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -14,8 +14,6 @@
14#include <QTimer> 14#include <QTimer>
15#include <QTreeView> 15#include <QTreeView>
16 16
17#include "common/common_paths.h"
18#include "common/file_util.h"
19#include "core/core.h" 17#include "core/core.h"
20#include "core/file_sys/control_metadata.h" 18#include "core/file_sys/control_metadata.h"
21#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index cdeeec01c..9b709d405 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -13,8 +13,8 @@
13#include <QTimer> 13#include <QTimer>
14#include <QTreeView> 14#include <QTreeView>
15 15
16#include "common/common_paths.h" 16#include "common/fs/fs.h"
17#include "common/file_util.h" 17#include "common/fs/path_util.h"
18#include "core/core.h" 18#include "core/core.h"
19#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
20#include "core/file_sys/xts_archive.h" 20#include "core/file_sys/xts_archive.h"
@@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {
79 std::sort(disabled_addons.begin(), disabled_addons.end()); 79 std::sort(disabled_addons.begin(), disabled_addons.end());
80 std::sort(current.begin(), current.end()); 80 std::sort(current.begin(), current.end());
81 if (disabled_addons != current) { 81 if (disabled_addons != current) {
82 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 82 void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
83 "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); 83 "game_list" / fmt::format("{:016X}.pv.txt", title_id)));
84 } 84 }
85 85
86 Settings::values.disabled_addons[title_id] = disabled_addons; 86 Settings::values.disabled_addons[title_id] = disabled_addons;
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index d61b5e29b..f5881e58d 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -12,7 +12,7 @@
12#include <QTreeView> 12#include <QTreeView>
13#include <QVBoxLayout> 13#include <QVBoxLayout>
14#include "common/assert.h" 14#include "common/assert.h"
15#include "common/file_util.h" 15#include "common/fs/path_util.h"
16#include "common/settings.h" 16#include "common/settings.h"
17#include "common/string_util.h" 17#include "common/string_util.h"
18#include "core/core.h" 18#include "core/core.h"
@@ -34,9 +34,10 @@ constexpr std::array<u8, 107> backup_jpeg{
34}; 34};
35 35
36QString GetImagePath(Common::UUID uuid) { 36QString GetImagePath(Common::UUID uuid) {
37 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 37 const auto path =
38 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 38 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
39 return QString::fromStdString(path); 39 fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch());
40 return QString::fromStdString(Common::FS::PathToUTF8String(path));
40} 41}
41 42
42QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) { 43QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {
@@ -281,8 +282,8 @@ void ConfigureProfileManager::SetUserImage() {
281 return; 282 return;
282 } 283 }
283 284
284 const auto raw_path = QString::fromStdString( 285 const auto raw_path = QString::fromStdString(Common::FS::PathToUTF8String(
285 Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010"); 286 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000010"));
286 const QFileInfo raw_info{raw_path}; 287 const QFileInfo raw_info{raw_path};
287 if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { 288 if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
288 QMessageBox::warning(this, tr("Error deleting file"), 289 QMessageBox::warning(this, tr("Error deleting file"),
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 85418f969..99a5df241 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -10,7 +10,6 @@
10#include <QGraphicsItem> 10#include <QGraphicsItem>
11#include <QMessageBox> 11#include <QMessageBox>
12#include "common/assert.h" 12#include "common/assert.h"
13#include "common/file_util.h"
14#include "common/settings.h" 13#include "common/settings.h"
15#include "core/core.h" 14#include "core/core.h"
16#include "core/hle/service/time/time.h" 15#include "core/hle/service/time/time.h"
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 0cdaea8a4..0a28c87c0 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -8,7 +8,7 @@
8 8
9#include <QDirIterator> 9#include <QDirIterator>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/file_util.h" 11#include "common/fs/path_util.h"
12#include "common/settings.h" 12#include "common/settings.h"
13#include "core/core.h" 13#include "core/core.h"
14#include "ui_configure_ui.h" 14#include "ui_configure_ui.h"
@@ -62,13 +62,16 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur
62 62
63 // Set screenshot path to user specification. 63 // Set screenshot path to user specification.
64 connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { 64 connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] {
65 const QString& filename = 65 auto dir =
66 QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), 66 QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."),
67 QString::fromStdString(Common::FS::GetUserPath( 67 QString::fromStdString(Common::FS::GetYuzuPathString(
68 Common::FS::UserPath::ScreenshotsDir))) + 68 Common::FS::YuzuPath::ScreenshotsDir)));
69 QDir::separator(); 69 if (!dir.isEmpty()) {
70 if (!filename.isEmpty()) { 70 if (dir.back() != QChar::fromLatin1('/')) {
71 ui->screenshot_path_edit->setText(filename); 71 dir.append(QChar::fromLatin1('/'));
72 }
73
74 ui->screenshot_path_edit->setText(dir);
72 } 75 }
73 }); 76 });
74} 77}
@@ -84,7 +87,7 @@ void ConfigureUi::ApplyConfiguration() {
84 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); 87 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
85 88
86 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); 89 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
87 Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, 90 Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir,
88 ui->screenshot_path_edit->text().toStdString()); 91 ui->screenshot_path_edit->text().toStdString());
89 Core::System::GetInstance().ApplySettings(); 92 Core::System::GetInstance().ApplySettings();
90} 93}
@@ -102,8 +105,8 @@ void ConfigureUi::SetConfiguration() {
102 ui->icon_size_combobox->findData(UISettings::values.icon_size)); 105 ui->icon_size_combobox->findData(UISettings::values.icon_size));
103 106
104 ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); 107 ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as);
105 ui->screenshot_path_edit->setText( 108 ui->screenshot_path_edit->setText(QString::fromStdString(
106 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir))); 109 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)));
107} 110}
108 111
109void ConfigureUi::changeEvent(QEvent* event) { 112void ConfigureUi::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index e87aededb..333eeb84e 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -4,8 +4,8 @@
4 4
5#include <fmt/format.h> 5#include <fmt/format.h>
6 6
7#include "common/common_paths.h" 7#include "common/fs/fs.h"
8#include "common/file_util.h" 8#include "common/fs/path_util.h"
9#include "yuzu/configuration/config.h" 9#include "yuzu/configuration/config.h"
10#include "yuzu/configuration/input_profiles.h" 10#include "yuzu/configuration/input_profiles.h"
11 11
@@ -14,47 +14,43 @@ namespace FS = Common::FS;
14namespace { 14namespace {
15 15
16bool ProfileExistsInFilesystem(std::string_view profile_name) { 16bool ProfileExistsInFilesystem(std::string_view profile_name) {
17 return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", 17 return FS::Exists(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input" /
18 FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); 18 fmt::format("{}.ini", profile_name));
19} 19}
20 20
21bool IsINI(std::string_view filename) { 21bool IsINI(const std::filesystem::path& filename) {
22 const std::size_t index = filename.rfind('.'); 22 return filename.extension() == ".ini";
23
24 if (index == std::string::npos) {
25 return false;
26 }
27
28 return filename.substr(index) == ".ini";
29} 23}
30 24
31std::string GetNameWithoutExtension(const std::string& filename) { 25std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
32 const std::size_t index = filename.rfind('.'); 26 return filename.replace_extension();
33
34 if (index == std::string::npos) {
35 return filename;
36 }
37
38 return filename.substr(0, index);
39} 27}
40 28
41} // namespace 29} // namespace
42 30
43InputProfiles::InputProfiles() { 31InputProfiles::InputProfiles() {
44 const std::string input_profile_loc = 32 const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
45 fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); 33
34 if (!FS::IsDir(input_profile_loc)) {
35 return;
36 }
46 37
47 FS::ForeachDirectoryEntry( 38 FS::IterateDirEntries(
48 nullptr, input_profile_loc, 39 input_profile_loc,
49 [this](u64* entries_out, const std::string& directory, const std::string& filename) { 40 [this](const std::filesystem::path& full_path) {
50 if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { 41 const auto filename = full_path.filename();
42 const auto name_without_ext =
43 Common::FS::PathToUTF8String(GetNameWithoutExtension(filename));
44
45 if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
51 map_profiles.insert_or_assign( 46 map_profiles.insert_or_assign(
52 GetNameWithoutExtension(filename), 47 name_without_ext,
53 std::make_unique<Config>(GetNameWithoutExtension(filename), 48 std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
54 Config::ConfigType::InputProfile));
55 } 49 }
50
56 return true; 51 return true;
57 }); 52 },
53 FS::DirEntryFilter::File);
58} 54}
59 55
60InputProfiles::~InputProfiles() = default; 56InputProfiles::~InputProfiles() = default;
@@ -96,7 +92,7 @@ bool InputProfiles::DeleteProfile(const std::string& profile_name) {
96 } 92 }
97 93
98 if (!ProfileExistsInFilesystem(profile_name) || 94 if (!ProfileExistsInFilesystem(profile_name) ||
99 FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { 95 FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) {
100 map_profiles.erase(profile_name); 96 map_profiles.erase(profile_name);
101 } 97 }
102 98
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 23643aea2..485045334 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -12,8 +12,8 @@
12#include <QFileInfo> 12#include <QFileInfo>
13#include <QSettings> 13#include <QSettings>
14 14
15#include "common/common_paths.h" 15#include "common/fs/fs.h"
16#include "common/file_util.h" 16#include "common/fs/path_util.h"
17#include "core/core.h" 17#include "core/core.h"
18#include "core/file_sys/card_image.h" 18#include "core/file_sys/card_image.h"
19#include "core/file_sys/content_archive.h" 19#include "core/file_sys/content_archive.h"
@@ -39,10 +39,11 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
39 return generator(); 39 return generator();
40 } 40 }
41 41
42 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 42 const auto path =
43 "game_list" + DIR_SEP + filename + '.' + ext; 43 Common::FS::PathToUTF8String(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
44 "game_list" / fmt::format("{}.{}", filename, ext));
44 45
45 Common::FS::CreateFullPath(path); 46 void(Common::FS::CreateParentDirs(path));
46 47
47 if (!Common::FS::Exists(path)) { 48 if (!Common::FS::Exists(path)) {
48 const auto str = generator(); 49 const auto str = generator();
@@ -70,12 +71,15 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
70 return generator(); 71 return generator();
71 } 72 }
72 73
73 const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 74 const auto game_list_dir =
74 "game_list" + DIR_SEP + filename + ".jpeg"; 75 Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list";
75 const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + 76 const auto jpeg_name = fmt::format("{}.jpeg", filename);
76 "game_list" + DIR_SEP + filename + ".appname.txt"; 77 const auto app_name = fmt::format("{}.appname.txt", filename);
77 78
78 Common::FS::CreateFullPath(path1); 79 const auto path1 = Common::FS::PathToUTF8String(game_list_dir / jpeg_name);
80 const auto path2 = Common::FS::PathToUTF8String(game_list_dir / app_name);
81
82 void(Common::FS::CreateParentDirs(path1));
79 83
80 if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { 84 if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) {
81 const auto [icon, nacp] = generator(); 85 const auto [icon, nacp] = generator();
@@ -281,23 +285,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
281 } 285 }
282} 286}
283 287
284void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, 288void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
285 unsigned int recursion, GameListDir* parent_dir) { 289 GameListDir* parent_dir) {
286 auto& system = Core::System::GetInstance(); 290 auto& system = Core::System::GetInstance();
287 291
288 const auto callback = [this, target, recursion, parent_dir, 292 const auto callback = [this, target, parent_dir,
289 &system](u64* num_entries_out, const std::string& directory, 293 &system](const std::filesystem::path& path) -> bool {
290 const std::string& virtual_name) -> bool {
291 if (stop_processing) { 294 if (stop_processing) {
292 // Breaks the callback loop. 295 // Breaks the callback loop.
293 return false; 296 return false;
294 } 297 }
295 298
296 const std::string physical_name = directory + DIR_SEP + virtual_name; 299 const auto physical_name = Common::FS::PathToUTF8String(path);
297 const bool is_dir = Common::FS::IsDirectory(physical_name); 300 const auto is_dir = Common::FS::IsDir(path);
301
298 if (!is_dir && 302 if (!is_dir &&
299 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { 303 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
300 const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); 304 const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
305 if (!file) {
306 return true;
307 }
308
301 auto loader = Loader::GetLoader(system, file); 309 auto loader = Loader::GetLoader(system, file);
302 if (!loader) { 310 if (!loader) {
303 return true; 311 return true;
@@ -343,15 +351,19 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
343 compatibility_list, patch), 351 compatibility_list, patch),
344 parent_dir); 352 parent_dir);
345 } 353 }
346 } else if (is_dir && recursion > 0) { 354 } else if (is_dir) {
347 watch_list.append(QString::fromStdString(physical_name)); 355 watch_list.append(QString::fromStdString(physical_name));
348 ScanFileSystem(target, physical_name, recursion - 1, parent_dir);
349 } 356 }
350 357
351 return true; 358 return true;
352 }; 359 };
353 360
354 Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback); 361 if (deep_scan) {
362 Common::FS::IterateDirEntriesRecursively(dir_path, callback,
363 Common::FS::DirEntryFilter::All);
364 } else {
365 Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File);
366 }
355} 367}
356 368
357void GameListWorker::run() { 369void GameListWorker::run() {
@@ -376,9 +388,9 @@ void GameListWorker::run() {
376 auto* const game_list_dir = new GameListDir(game_dir); 388 auto* const game_list_dir = new GameListDir(game_dir);
377 emit DirEntryReady(game_list_dir); 389 emit DirEntryReady(game_list_dir);
378 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 390 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
379 game_dir.deep_scan ? 256 : 0, game_list_dir); 391 game_dir.deep_scan, game_list_dir);
380 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), 392 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
381 game_dir.deep_scan ? 256 : 0, game_list_dir); 393 game_dir.deep_scan, game_list_dir);
382 } 394 }
383 } 395 }
384 396
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 84e4e1b42..396bb2623 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -70,7 +70,7 @@ private:
70 PopulateGameList, 70 PopulateGameList,
71 }; 71 };
72 72
73 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, 73 void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
74 GameListDir* parent_dir); 74 GameListDir* parent_dir);
75 75
76 std::shared_ptr<FileSys::VfsFilesystem> vfs; 76 std::shared_ptr<FileSys::VfsFilesystem> vfs;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index bc97f9d53..37ef62967 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -66,9 +66,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
66#include <QtConcurrent/QtConcurrent> 66#include <QtConcurrent/QtConcurrent>
67 67
68#include <fmt/format.h> 68#include <fmt/format.h>
69#include "common/common_paths.h"
70#include "common/detached_tasks.h" 69#include "common/detached_tasks.h"
71#include "common/file_util.h" 70#include "common/fs/fs.h"
71#include "common/fs/fs_paths.h"
72#include "common/fs/path_util.h"
72#include "common/logging/backend.h" 73#include "common/logging/backend.h"
73#include "common/logging/filter.h" 74#include "common/logging/filter.h"
74#include "common/logging/log.h" 75#include "common/logging/log.h"
@@ -178,36 +179,25 @@ static void InitializeLogging() {
178 log_filter.ParseFilterString(Settings::values.log_filter); 179 log_filter.ParseFilterString(Settings::values.log_filter);
179 Log::SetGlobalFilter(log_filter); 180 Log::SetGlobalFilter(log_filter);
180 181
181 const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); 182 const auto log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir);
182 FS::CreateFullPath(log_dir); 183 void(FS::CreateDir(log_dir));
183 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); 184 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE));
184#ifdef _WIN32 185#ifdef _WIN32
185 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); 186 Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
186#endif 187#endif
187} 188}
188 189
189static void RemoveCachedContents() { 190static void RemoveCachedContents() {
190 const auto offline_fonts = Common::FS::SanitizePath( 191 const auto cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir);
191 fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), 192 const auto offline_fonts = cache_dir / "fonts";
192 Common::FS::DirectorySeparator::PlatformDefault); 193 const auto offline_manual = cache_dir / "offline_web_applet_manual";
193 194 const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
194 const auto offline_manual = Common::FS::SanitizePath( 195 const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
195 fmt::format("{}/offline_web_applet_manual", 196
196 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), 197 void(Common::FS::RemoveDirRecursively(offline_fonts));
197 Common::FS::DirectorySeparator::PlatformDefault); 198 void(Common::FS::RemoveDirRecursively(offline_manual));
198 const auto offline_legal_information = Common::FS::SanitizePath( 199 void(Common::FS::RemoveDirRecursively(offline_legal_information));
199 fmt::format("{}/offline_web_applet_legal_information", 200 void(Common::FS::RemoveDirRecursively(offline_system_data));
200 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
201 Common::FS::DirectorySeparator::PlatformDefault);
202 const auto offline_system_data = Common::FS::SanitizePath(
203 fmt::format("{}/offline_web_applet_system_data",
204 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
205 Common::FS::DirectorySeparator::PlatformDefault);
206
207 Common::FS::DeleteDirRecursively(offline_fonts);
208 Common::FS::DeleteDirRecursively(offline_manual);
209 Common::FS::DeleteDirRecursively(offline_legal_information);
210 Common::FS::DeleteDirRecursively(offline_system_data);
211} 201}
212 202
213GMainWindow::GMainWindow() 203GMainWindow::GMainWindow()
@@ -1418,7 +1408,8 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) {
1418 title_name = metadata.first->GetApplicationName(); 1408 title_name = metadata.first->GetApplicationName();
1419 } 1409 }
1420 if (res != Loader::ResultStatus::Success || title_name.empty()) { 1410 if (res != Loader::ResultStatus::Success || title_name.empty()) {
1421 title_name = Common::FS::GetFilename(filename.toStdString()); 1411 title_name = Common::FS::PathToUTF8String(
1412 std::filesystem::path{filename.toStdU16String()}.filename());
1422 } 1413 }
1423 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); 1414 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
1424 UpdateWindowTitle(title_name, title_version); 1415 UpdateWindowTitle(title_name, title_version);
@@ -1538,7 +1529,7 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
1538 1529
1539void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, 1530void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
1540 const std::string& game_path) { 1531 const std::string& game_path) {
1541 std::string path; 1532 std::filesystem::path path;
1542 QString open_target; 1533 QString open_target;
1543 auto& system = Core::System::GetInstance(); 1534 auto& system = Core::System::GetInstance();
1544 1535
@@ -1567,7 +1558,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
1567 switch (target) { 1558 switch (target) {
1568 case GameListOpenTarget::SaveData: { 1559 case GameListOpenTarget::SaveData: {
1569 open_target = tr("Save Data"); 1560 open_target = tr("Save Data");
1570 const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir); 1561 const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir);
1571 1562
1572 if (has_user_save) { 1563 if (has_user_save) {
1573 // User save data 1564 // User save data
@@ -1592,34 +1583,38 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
1592 Service::Account::ProfileManager manager; 1583 Service::Account::ProfileManager manager;
1593 const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); 1584 const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
1594 ASSERT(user_id); 1585 ASSERT(user_id);
1595 path = nand_dir + FileSys::SaveDataFactory::GetFullPath( 1586
1596 system, FileSys::SaveDataSpaceId::NandUser, 1587 const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
1597 FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0); 1588 system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
1589 program_id, user_id->uuid, 0);
1590
1591 path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
1598 } else { 1592 } else {
1599 // Device save data 1593 // Device save data
1600 path = nand_dir + FileSys::SaveDataFactory::GetFullPath( 1594 const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath(
1601 system, FileSys::SaveDataSpaceId::NandUser, 1595 system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
1602 FileSys::SaveDataType::SaveData, program_id, {}, 0); 1596 program_id, {}, 0);
1597
1598 path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path);
1603 } 1599 }
1604 1600
1605 if (!Common::FS::Exists(path)) { 1601 if (!Common::FS::CreateDirs(path)) {
1606 Common::FS::CreateFullPath(path); 1602 LOG_ERROR(Frontend, "Unable to create the directories for save data");
1607 Common::FS::CreateDir(path);
1608 } 1603 }
1609 1604
1610 break; 1605 break;
1611 } 1606 }
1612 case GameListOpenTarget::ModData: { 1607 case GameListOpenTarget::ModData: {
1613 open_target = tr("Mod Data"); 1608 open_target = tr("Mod Data");
1614 const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir); 1609 path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir) /
1615 path = fmt::format("{}{:016X}", load_dir, program_id); 1610 fmt::format("{:016X}", program_id);
1616 break; 1611 break;
1617 } 1612 }
1618 default: 1613 default:
1619 UNIMPLEMENTED(); 1614 UNIMPLEMENTED();
1620 } 1615 }
1621 1616
1622 const QString qpath = QString::fromStdString(path); 1617 const QString qpath = QString::fromStdString(Common::FS::PathToUTF8String(path));
1623 const QDir dir(qpath); 1618 const QDir dir(qpath);
1624 if (!dir.exists()) { 1619 if (!dir.exists()) {
1625 QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target), 1620 QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target),
@@ -1632,33 +1627,35 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
1632} 1627}
1633 1628
1634void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { 1629void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
1635 const QString shader_dir = 1630 const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir);
1636 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); 1631 const auto transferable_shader_cache_folder_path = shader_cache_dir / "opengl" / "transferable";
1637 const QString transferable_shader_cache_folder_path = 1632 const auto transferable_shader_cache_file_path =
1638 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); 1633 transferable_shader_cache_folder_path / fmt::format("{:016X}.bin", program_id);
1639 const QString transferable_shader_cache_file_path = 1634
1640 transferable_shader_cache_folder_path + QDir::separator() + 1635 if (!Common::FS::Exists(transferable_shader_cache_file_path)) {
1641 QString::fromStdString(fmt::format("{:016X}.bin", program_id));
1642
1643 if (!QFile::exists(transferable_shader_cache_file_path)) {
1644 QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), 1636 QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"),
1645 tr("A shader cache for this title does not exist.")); 1637 tr("A shader cache for this title does not exist."));
1646 return; 1638 return;
1647 } 1639 }
1648 1640
1641 const auto qt_shader_cache_folder_path =
1642 QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_folder_path));
1643 const auto qt_shader_cache_file_path =
1644 QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_file_path));
1645
1649 // Windows supports opening a folder with selecting a specified file in explorer. On every other 1646 // Windows supports opening a folder with selecting a specified file in explorer. On every other
1650 // OS we just open the transferable shader cache folder without preselecting the transferable 1647 // OS we just open the transferable shader cache folder without preselecting the transferable
1651 // shader cache file for the selected game. 1648 // shader cache file for the selected game.
1652#if defined(Q_OS_WIN) 1649#if defined(Q_OS_WIN)
1653 const QString explorer = QStringLiteral("explorer"); 1650 const QString explorer = QStringLiteral("explorer");
1654 QStringList param; 1651 QStringList param;
1655 if (!QFileInfo(transferable_shader_cache_file_path).isDir()) { 1652 if (!QFileInfo(qt_shader_cache_file_path).isDir()) {
1656 param << QStringLiteral("/select,"); 1653 param << QStringLiteral("/select,");
1657 } 1654 }
1658 param << QDir::toNativeSeparators(transferable_shader_cache_file_path); 1655 param << QDir::toNativeSeparators(qt_shader_cache_file_path);
1659 QProcess::startDetached(explorer, param); 1656 QProcess::startDetached(explorer, param);
1660#else 1657#else
1661 QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); 1658 QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_folder_path));
1662#endif 1659#endif
1663} 1660}
1664 1661
@@ -1736,8 +1733,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
1736 RemoveAddOnContent(program_id, entry_type); 1733 RemoveAddOnContent(program_id, entry_type);
1737 break; 1734 break;
1738 } 1735 }
1739 Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + 1736 void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
1740 DIR_SEP + "game_list"); 1737 "game_list"));
1741 game_list->PopulateAsync(UISettings::values.game_dirs); 1738 game_list->PopulateAsync(UISettings::values.game_dirs);
1742} 1739}
1743 1740
@@ -1826,21 +1823,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
1826} 1823}
1827 1824
1828void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { 1825void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
1829 const QString shader_dir = 1826 const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir);
1830 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); 1827 const auto transferable_shader_cache_file_path =
1831 const QString transferable_shader_cache_folder_path = 1828 shader_cache_dir / "opengl" / "transferable" / fmt::format("{:016X}.bin", program_id);
1832 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); 1829
1833 const QString transferable_shader_cache_file_path = 1830 if (!Common::FS::Exists(transferable_shader_cache_file_path)) {
1834 transferable_shader_cache_folder_path + QDir::separator() +
1835 QString::fromStdString(fmt::format("{:016X}.bin", program_id));
1836
1837 if (!QFile::exists(transferable_shader_cache_file_path)) {
1838 QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), 1831 QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"),
1839 tr("A shader cache for this title does not exist.")); 1832 tr("A shader cache for this title does not exist."));
1840 return; 1833 return;
1841 } 1834 }
1842 1835
1843 if (QFile::remove(transferable_shader_cache_file_path)) { 1836 if (Common::FS::RemoveFile(transferable_shader_cache_file_path)) {
1844 QMessageBox::information(this, tr("Successfully Removed"), 1837 QMessageBox::information(this, tr("Successfully Removed"),
1845 tr("Successfully removed the transferable shader cache.")); 1838 tr("Successfully removed the transferable shader cache."));
1846 } else { 1839 } else {
@@ -1850,19 +1843,16 @@ void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
1850} 1843}
1851 1844
1852void GMainWindow::RemoveCustomConfiguration(u64 program_id) { 1845void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
1853 const QString config_dir = 1846 const auto custom_config_file_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) /
1854 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); 1847 "custom" / fmt::format("{:016X}.ini", program_id);
1855 const QString custom_config_file_path =
1856 config_dir + QStringLiteral("custom") + QDir::separator() +
1857 QString::fromStdString(fmt::format("{:016X}.ini", program_id));
1858 1848
1859 if (!QFile::exists(custom_config_file_path)) { 1849 if (!Common::FS::Exists(custom_config_file_path)) {
1860 QMessageBox::warning(this, tr("Error Removing Custom Configuration"), 1850 QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
1861 tr("A custom configuration for this title does not exist.")); 1851 tr("A custom configuration for this title does not exist."));
1862 return; 1852 return;
1863 } 1853 }
1864 1854
1865 if (QFile::remove(custom_config_file_path)) { 1855 if (Common::FS::RemoveFile(custom_config_file_path)) {
1866 QMessageBox::information(this, tr("Successfully Removed"), 1856 QMessageBox::information(this, tr("Successfully Removed"),
1867 tr("Successfully removed the custom game configuration.")); 1857 tr("Successfully removed the custom game configuration."));
1868 } else { 1858 } else {
@@ -1899,8 +1889,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1899 return; 1889 return;
1900 } 1890 }
1901 1891
1902 const auto path = fmt::format( 1892 const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir);
1903 "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id); 1893 const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);
1894
1895 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
1904 1896
1905 FileSys::VirtualFile romfs; 1897 FileSys::VirtualFile romfs;
1906 1898
@@ -1978,24 +1970,29 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1978} 1970}
1979 1971
1980void GMainWindow::OnGameListOpenDirectory(const QString& directory) { 1972void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1981 QString path; 1973 std::filesystem::path fs_path;
1982 if (directory == QStringLiteral("SDMC")) { 1974 if (directory == QStringLiteral("SDMC")) {
1983 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + 1975 fs_path =
1984 "Nintendo/Contents/registered"); 1976 Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/registered";
1985 } else if (directory == QStringLiteral("UserNAND")) { 1977 } else if (directory == QStringLiteral("UserNAND")) {
1986 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 1978 fs_path =
1987 "user/Contents/registered"); 1979 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "user/Contents/registered";
1988 } else if (directory == QStringLiteral("SysNAND")) { 1980 } else if (directory == QStringLiteral("SysNAND")) {
1989 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + 1981 fs_path =
1990 "system/Contents/registered"); 1982 Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/Contents/registered";
1991 } else { 1983 } else {
1992 path = directory; 1984 fs_path = directory.toStdString();
1993 } 1985 }
1994 if (!QFileInfo::exists(path)) { 1986
1995 QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); 1987 const auto qt_path = QString::fromStdString(Common::FS::PathToUTF8String(fs_path));
1988
1989 if (!Common::FS::IsDir(fs_path)) {
1990 QMessageBox::critical(this, tr("Error Opening %1").arg(qt_path),
1991 tr("Folder does not exist!"));
1996 return; 1992 return;
1997 } 1993 }
1998 QDesktopServices::openUrl(QUrl::fromLocalFile(path)); 1994
1995 QDesktopServices::openUrl(QUrl::fromLocalFile(qt_path));
1999} 1996}
2000 1997
2001void GMainWindow::OnGameListAddDirectory() { 1998void GMainWindow::OnGameListAddDirectory() {
@@ -2189,8 +2186,8 @@ void GMainWindow::OnMenuInstallToNAND() {
2189 : tr("%n file(s) failed to install\n", "", failed_files.size())); 2186 : tr("%n file(s) failed to install\n", "", failed_files.size()));
2190 2187
2191 QMessageBox::information(this, tr("Install Results"), install_results); 2188 QMessageBox::information(this, tr("Install Results"), install_results);
2192 Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + 2189 void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
2193 DIR_SEP + "game_list"); 2190 "game_list"));
2194 game_list->PopulateAsync(UISettings::values.game_dirs); 2191 game_list->PopulateAsync(UISettings::values.game_dirs);
2195 ui.action_Install_File_NAND->setEnabled(true); 2192 ui.action_Install_File_NAND->setEnabled(true);
2196} 2193}
@@ -2706,7 +2703,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
2706 2703
2707void GMainWindow::OnOpenYuzuFolder() { 2704void GMainWindow::OnOpenYuzuFolder() {
2708 QDesktopServices::openUrl(QUrl::fromLocalFile( 2705 QDesktopServices::openUrl(QUrl::fromLocalFile(
2709 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir)))); 2706 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
2710} 2707}
2711 2708
2712void GMainWindow::OnAbout() { 2709void GMainWindow::OnAbout() {
@@ -2728,7 +2725,7 @@ void GMainWindow::OnCaptureScreenshot() {
2728 2725
2729 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 2726 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
2730 const auto screenshot_path = 2727 const auto screenshot_path =
2731 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)); 2728 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir));
2732 const auto date = 2729 const auto date =
2733 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); 2730 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz"));
2734 QString filename = QStringLiteral("%1%2_%3.png") 2731 QString filename = QStringLiteral("%1%2_%3.png")
@@ -2757,23 +2754,26 @@ void GMainWindow::OnCaptureScreenshot() {
2757 2754
2758// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant 2755// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
2759void GMainWindow::MigrateConfigFiles() { 2756void GMainWindow::MigrateConfigFiles() {
2760 const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir); 2757 const auto config_dir_fs_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir);
2761 const QDir config_dir = QDir(QString::fromStdString(config_dir_str)); 2758 const QDir config_dir =
2759 QDir(QString::fromStdString(Common::FS::PathToUTF8String(config_dir_fs_path)));
2762 const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini"))); 2760 const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini")));
2763 2761
2764 Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str)); 2762 if (!Common::FS::CreateDirs(config_dir_fs_path / "custom")) {
2765 for (QStringList::const_iterator it = config_dir_list.constBegin(); 2763 LOG_ERROR(Frontend, "Failed to create new config file directory");
2766 it != config_dir_list.constEnd(); ++it) { 2764 }
2765
2766 for (auto it = config_dir_list.constBegin(); it != config_dir_list.constEnd(); ++it) {
2767 const auto filename = it->toStdString(); 2767 const auto filename = it->toStdString();
2768 if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) { 2768 if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) {
2769 continue; 2769 continue;
2770 } 2770 }
2771 const auto origin = fmt::format("{}{}", config_dir_str, filename); 2771 const auto origin = config_dir_fs_path / filename;
2772 const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename); 2772 const auto destination = config_dir_fs_path / "custom" / filename;
2773 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); 2773 LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
2774 if (!Common::FS::Rename(origin, destination)) { 2774 if (!Common::FS::RenameFile(origin, destination)) {
2775 // Delete the old config file if one already exists in the new location. 2775 // Delete the old config file if one already exists in the new location.
2776 Common::FS::Delete(origin); 2776 void(Common::FS::RemoveFile(origin));
2777 } 2777 }
2778 } 2778 }
2779} 2779}
@@ -2965,18 +2965,16 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
2965 if (res == QMessageBox::Cancel) 2965 if (res == QMessageBox::Cancel)
2966 return; 2966 return;
2967 2967
2968 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + 2968 const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
2969 "prod.keys_autogenerated"); 2969
2970 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + 2970 void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated"));
2971 "console.keys_autogenerated"); 2971 void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated"));
2972 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + 2972 void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated"));
2973 "title.keys_autogenerated");
2974 } 2973 }
2975 2974
2976 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 2975 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
2977 if (keys.BaseDeriveNecessary()) { 2976 if (keys.BaseDeriveNecessary()) {
2978 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( 2977 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)};
2979 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)};
2980 2978
2981 const auto function = [this, &keys, &pdm] { 2979 const auto function = [this, &keys, &pdm] {
2982 keys.PopulateFromPartitionData(pdm); 2980 keys.PopulateFromPartitionData(pdm);
@@ -3289,12 +3287,17 @@ int main(int argc, char* argv[]) {
3289 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); 3287 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
3290 QCoreApplication::setApplicationName(QStringLiteral("yuzu")); 3288 QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
3291 3289
3290#ifdef _WIN32
3291 // Increases the maximum open file limit to 4096
3292 _setmaxstdio(4096);
3293#endif
3294
3292#ifdef __APPLE__ 3295#ifdef __APPLE__
3293 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". 3296 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
3294 // But since we require the working directory to be the executable path for the location of 3297 // But since we require the working directory to be the executable path for the location of
3295 // the user folder in the Qt Frontend, we need to cd into that working directory 3298 // the user folder in the Qt Frontend, we need to cd into that working directory
3296 const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + ".."; 3299 const auto bin_path = Common::FS::GetBundleDirectory() / "..";
3297 chdir(bin_path.c_str()); 3300 chdir(Common::FS::PathToUTF8String(bin_path).c_str());
3298#endif 3301#endif
3299 3302
3300#ifdef __linux__ 3303#ifdef __linux__
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 38d896d65..a2ab69cdd 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -16,7 +16,9 @@
16#endif 16#endif
17 17
18#include <inih/cpp/INIReader.h> 18#include <inih/cpp/INIReader.h>
19#include "common/file_util.h" 19#include "common/fs/file.h"
20#include "common/fs/fs.h"
21#include "common/fs/path_util.h"
20#include "common/logging/log.h" 22#include "common/logging/log.h"
21#include "common/param_package.h" 23#include "common/param_package.h"
22#include "common/settings.h" 24#include "common/settings.h"
@@ -30,8 +32,8 @@ namespace FS = Common::FS;
30 32
31Config::Config() { 33Config::Config() {
32 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 34 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
33 sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini"; 35 sdl2_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
34 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); 36 sdl2_config = std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc));
35 37
36 Reload(); 38 Reload();
37} 39}
@@ -39,20 +41,23 @@ Config::Config() {
39Config::~Config() = default; 41Config::~Config() = default;
40 42
41bool Config::LoadINI(const std::string& default_contents, bool retry) { 43bool Config::LoadINI(const std::string& default_contents, bool retry) {
42 const std::string& location = this->sdl2_config_loc; 44 const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc);
43 if (sdl2_config->ParseError() < 0) { 45 if (sdl2_config->ParseError() < 0) {
44 if (retry) { 46 if (retry) {
45 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); 47 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
46 FS::CreateFullPath(location); 48 config_loc_str);
47 FS::WriteStringToFile(true, location, default_contents); 49
48 sdl2_config = std::make_unique<INIReader>(location); // Reopen file 50 void(FS::CreateParentDir(sdl2_config_loc));
51 void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents));
52
53 sdl2_config = std::make_unique<INIReader>(config_loc_str);
49 54
50 return LoadINI(default_contents, false); 55 return LoadINI(default_contents, false);
51 } 56 }
52 LOG_ERROR(Config, "Failed."); 57 LOG_ERROR(Config, "Failed.");
53 return false; 58 return false;
54 } 59 }
55 LOG_INFO(Config, "Successfully loaded {}", location); 60 LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
56 return true; 61 return true;
57} 62}
58 63
@@ -327,18 +332,18 @@ void Config::ReadValues() {
327 // Data Storage 332 // Data Storage
328 Settings::values.use_virtual_sd = 333 Settings::values.use_virtual_sd =
329 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); 334 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
330 FS::GetUserPath( 335 FS::SetYuzuPath(FS::YuzuPath::NANDDir,
331 FS::UserPath::NANDDir, 336 sdl2_config->Get("Data Storage", "nand_directory",
332 sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir))); 337 FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
333 FS::GetUserPath( 338 FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
334 FS::UserPath::SDMCDir, 339 sdl2_config->Get("Data Storage", "sdmc_directory",
335 sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir))); 340 FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
336 FS::GetUserPath( 341 FS::SetYuzuPath(FS::YuzuPath::LoadDir,
337 FS::UserPath::LoadDir, 342 sdl2_config->Get("Data Storage", "load_directory",
338 sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir))); 343 FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
339 FS::GetUserPath( 344 FS::SetYuzuPath(FS::YuzuPath::DumpDir,
340 FS::UserPath::DumpDir, 345 sdl2_config->Get("Data Storage", "dump_directory",
341 sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); 346 FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
342 Settings::values.gamecard_inserted = 347 Settings::values.gamecard_inserted =
343 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); 348 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
344 Settings::values.gamecard_current_game = 349 Settings::values.gamecard_current_game =
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
index abc90f642..807199278 100644
--- a/src/yuzu_cmd/config.h
+++ b/src/yuzu_cmd/config.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <filesystem>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9 10
@@ -11,7 +12,7 @@ class INIReader;
11 12
12class Config { 13class Config {
13 std::unique_ptr<INIReader> sdl2_config; 14 std::unique_ptr<INIReader> sdl2_config;
14 std::string sdl2_config_loc; 15 std::filesystem::path sdl2_config_loc;
15 16
16 bool LoadINI(const std::string& default_contents = "", bool retry = true); 17 bool LoadINI(const std::string& default_contents = "", bool retry = true);
17 void ReadValues(); 18 void ReadValues();
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index e2812ca61..584967f5c 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -10,9 +10,10 @@
10 10
11#include <fmt/ostream.h> 11#include <fmt/ostream.h>
12 12
13#include "common/common_paths.h"
14#include "common/detached_tasks.h" 13#include "common/detached_tasks.h"
15#include "common/file_util.h" 14#include "common/fs/fs.h"
15#include "common/fs/fs_paths.h"
16#include "common/fs/path_util.h"
16#include "common/logging/backend.h" 17#include "common/logging/backend.h"
17#include "common/logging/filter.h" 18#include "common/logging/filter.h"
18#include "common/logging/log.h" 19#include "common/logging/log.h"
@@ -82,9 +83,9 @@ static void InitializeLogging() {
82 83
83 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); 84 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
84 85
85 const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); 86 const auto& log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir);
86 FS::CreateFullPath(log_dir); 87 void(FS::CreateDir(log_dir));
87 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); 88 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE));
88#ifdef _WIN32 89#ifdef _WIN32
89 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); 90 Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
90#endif 91#endif