summaryrefslogtreecommitdiff
path: root/src/common/file_util.cpp
diff options
context:
space:
mode:
authorGravatar Morph2021-05-25 19:32:56 -0400
committerGravatar GitHub2021-05-25 19:32:56 -0400
commit065867e2c24e9856c360fc2d6b9a86c92aedc43e (patch)
tree7964e85ef4f01a3c2b8f44e850f37b384405b930 /src/common/file_util.cpp
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
Diffstat (limited to 'src/common/file_util.cpp')
-rw-r--r--src/common/file_util.cpp1032
1 files changed, 0 insertions, 1032 deletions
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