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 #include "core/settings.h"
16 
17 #ifdef _WIN32
18 #include <windows.h>
19 // windows.h needs to be included before other windows headers
20 #include <direct.h> // getcwd
21 #include <io.h>
22 #include <shellapi.h>
23 #include <shlobj.h> // for SHGetFolderPath
24 #include <tchar.h>
25 #include "common/string_util.h"
26 
27 #ifdef _MSC_VER
28 // 64 bit offsets for MSVC
29 #define fseeko _fseeki64
30 #define ftello _ftelli64
31 #define fileno _fileno
32 #endif
33 
34 // 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64
35 #define stat _stat64
36 #define fstat _fstat64
37 
38 #else
39 #ifdef __APPLE__
40 #include <sys/param.h>
41 #endif
42 #include <cctype>
43 #include <cerrno>
44 #include <cstdlib>
45 #include <cstring>
46 #include <dirent.h>
47 #include <pwd.h>
48 #include <unistd.h>
49 #endif
50 
51 #if defined(__APPLE__)
52 // CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
53 // ignore them if we're not using clang. The macro is only used to prevent linking against
54 // functions that don't exist on older versions of macOS, and the worst case scenario is a linker
55 // error, so this is perfectly safe, just inconvenient.
56 #ifndef __clang__
57 #define availability(...)
58 #endif
59 #include <CoreFoundation/CFBundle.h>
60 #include <CoreFoundation/CFString.h>
61 #include <CoreFoundation/CFURL.h>
62 #ifdef availability
63 #undef availability
64 #endif
65 
66 #endif
67 
68 #include <algorithm>
69 #include <sys/stat.h>
70 
71 #ifndef S_ISDIR
72 #define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
73 #endif
74 
75 // This namespace has various generic functions related to files and paths.
76 // The code still needs a ton of cleanup.
77 // REMEMBER: strdup considered harmful!
78 namespace FileUtil {
79 
80 // Remove any ending forward slashes from directory paths
81 // Modifies argument.
StripTailDirSlashes(std::string & fname)82 static void StripTailDirSlashes(std::string& fname) {
83     if (fname.length() <= 1) {
84         return;
85     }
86 
87     std::size_t i = fname.length();
88     while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
89         --i;
90     }
91     fname.resize(i);
92 }
93 
Exists(const std::string & filename)94 bool Exists(const std::string& filename) {
95     struct stat file_info;
96 
97     std::string copy(filename);
98     StripTailDirSlashes(copy);
99 
100 #ifdef _WIN32
101     // Windows needs a slash to identify a driver root
102     if (copy.size() != 0 && copy.back() == ':')
103         copy += DIR_SEP_CHR;
104 
105     int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
106 #else
107     int result = stat(copy.c_str(), &file_info);
108 #endif
109 
110     return (result == 0);
111 }
112 
IsDirectory(const std::string & filename)113 bool IsDirectory(const std::string& filename) {
114     struct stat file_info;
115 
116     std::string copy(filename);
117     StripTailDirSlashes(copy);
118 
119 #ifdef _WIN32
120     // Windows needs a slash to identify a driver root
121     if (copy.size() != 0 && copy.back() == ':')
122         copy += DIR_SEP_CHR;
123 
124     int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
125 #else
126     int result = stat(copy.c_str(), &file_info);
127 #endif
128 
129     if (result < 0) {
130         LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
131         return false;
132     }
133 
134     return S_ISDIR(file_info.st_mode);
135 }
136 
Delete(const std::string & filename)137 bool Delete(const std::string& filename) {
138     LOG_TRACE(Common_Filesystem, "file {}", filename);
139 
140     // Return true because we care about the file no
141     // being there, not the actual delete.
142     if (!Exists(filename)) {
143         LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
144         return true;
145     }
146 
147     // We can't delete a directory
148     if (IsDirectory(filename)) {
149         LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
150         return false;
151     }
152 
153 #ifdef _WIN32
154     if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
155         LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
156         return false;
157     }
158 #else
159     if (unlink(filename.c_str()) == -1) {
160         LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
161         return false;
162     }
163 #endif
164 
165     return true;
166 }
167 
CreateDir(const std::string & path)168 bool CreateDir(const std::string& path) {
169     LOG_TRACE(Common_Filesystem, "directory {}", path);
170 #ifdef _WIN32
171     if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
172         return true;
173     DWORD error = GetLastError();
174     if (error == ERROR_ALREADY_EXISTS) {
175         LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
176         return true;
177     }
178     LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
179     return false;
180 #else
181     if (mkdir(path.c_str(), 0755) == 0)
182         return true;
183 
184     int err = errno;
185 
186     if (err == EEXIST) {
187         LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
188         return true;
189     }
190 
191     LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
192     return false;
193 #endif
194 }
195 
CreateFullPath(const std::string & fullPath)196 bool CreateFullPath(const std::string& fullPath) {
197     int panicCounter = 100;
198     LOG_TRACE(Common_Filesystem, "path {}", fullPath);
199 
200     if (FileUtil::Exists(fullPath)) {
201         LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
202         return true;
203     }
204 
205     std::size_t position = 0;
206     while (true) {
207         // Find next sub path
208         position = fullPath.find(DIR_SEP_CHR, position);
209 
210         // we're done, yay!
211         if (position == fullPath.npos)
212             return true;
213 
214         // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
215         std::string const subPath(fullPath.substr(0, position + 1));
216         if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) {
217             LOG_ERROR(Common, "CreateFullPath: directory creation failed");
218             return false;
219         }
220 
221         // A safety check
222         panicCounter--;
223         if (panicCounter <= 0) {
224             LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
225             return false;
226         }
227         position++;
228     }
229 }
230 
DeleteDir(const std::string & filename)231 bool DeleteDir(const std::string& filename) {
232     LOG_TRACE(Common_Filesystem, "directory {}", filename);
233 
234     // check if a directory
235     if (!FileUtil::IsDirectory(filename)) {
236         LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
237         return false;
238     }
239 
240 #ifdef _WIN32
241     if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
242         return true;
243 #else
244     if (rmdir(filename.c_str()) == 0)
245         return true;
246 #endif
247     LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
248 
249     return false;
250 }
251 
Rename(const std::string & srcFilename,const std::string & destFilename)252 bool Rename(const std::string& srcFilename, const std::string& destFilename) {
253     LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
254 #ifdef _WIN32
255     if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
256                  Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
257         return true;
258 #else
259     if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
260         return true;
261 #endif
262     LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
263               GetLastErrorMsg());
264     return false;
265 }
266 
Copy(const std::string & srcFilename,const std::string & destFilename)267 bool Copy(const std::string& srcFilename, const std::string& destFilename) {
268     LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
269 #ifdef _WIN32
270     if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
271                   Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
272         return true;
273 
274     LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
275               GetLastErrorMsg());
276     return false;
277 #else
278     using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
279 
280     // Open input file
281     CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
282     if (!input) {
283         LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
284                   destFilename, GetLastErrorMsg());
285         return false;
286     }
287 
288     // open output file
289     CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
290     if (!output) {
291         LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
292                   destFilename, GetLastErrorMsg());
293         return false;
294     }
295 
296     // copy loop
297     std::array<char, 1024> buffer;
298     while (!feof(input.get())) {
299         // read input
300         std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
301         if (rnum != buffer.size()) {
302             if (ferror(input.get()) != 0) {
303                 LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
304                           srcFilename, destFilename, GetLastErrorMsg());
305                 return false;
306             }
307         }
308 
309         // write output
310         std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
311         if (wnum != rnum) {
312             LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
313                       destFilename, GetLastErrorMsg());
314             return false;
315         }
316     }
317 
318     return true;
319 #endif
320 }
321 
GetSize(const std::string & filename)322 u64 GetSize(const std::string& filename) {
323     if (!Exists(filename)) {
324         LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
325         return 0;
326     }
327 
328     if (IsDirectory(filename)) {
329         LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
330         return 0;
331     }
332 
333     struct stat buf;
334 #ifdef _WIN32
335     if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
336 #else
337     if (stat(filename.c_str(), &buf) == 0)
338 #endif
339     {
340         LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
341         return buf.st_size;
342     }
343 
344     LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
345     return 0;
346 }
347 
GetSize(const int fd)348 u64 GetSize(const int fd) {
349     struct stat buf;
350     if (fstat(fd, &buf) != 0) {
351         LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
352         return 0;
353     }
354     return buf.st_size;
355 }
356 
GetSize(FILE * f)357 u64 GetSize(FILE* f) {
358     // can't use off_t here because it can be 32-bit
359     u64 pos = ftello(f);
360     if (fseeko(f, 0, SEEK_END) != 0) {
361         LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
362         return 0;
363     }
364     u64 size = ftello(f);
365     if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) {
366         LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg());
367         return 0;
368     }
369     return size;
370 }
371 
CreateEmptyFile(const std::string & filename)372 bool CreateEmptyFile(const std::string& filename) {
373     LOG_TRACE(Common_Filesystem, "{}", filename);
374 
375     if (!FileUtil::IOFile(filename, "wb").IsOpen()) {
376         LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
377         return false;
378     }
379 
380     return true;
381 }
382 
ForeachDirectoryEntry(u64 * num_entries_out,const std::string & directory,DirectoryEntryCallable callback)383 bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
384                            DirectoryEntryCallable callback) {
385     LOG_TRACE(Common_Filesystem, "directory {}", directory);
386 
387     // How many files + directories we found
388     u64 found_entries = 0;
389 
390     // Save the status of callback function
391     bool callback_error = false;
392 
393 #ifdef _WIN32
394     // Find the first file in the directory.
395     WIN32_FIND_DATAW ffd;
396 
397     HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd);
398     if (handle_find == INVALID_HANDLE_VALUE) {
399         FindClose(handle_find);
400         return false;
401     }
402     // windows loop
403     do {
404         const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
405 #else
406     DIR* dirp = opendir(directory.c_str());
407     if (!dirp)
408         return false;
409 
410     // non windows loop
411     while (struct dirent* result = readdir(dirp)) {
412         const std::string virtual_name(result->d_name);
413 #endif
414 
415         if (virtual_name == "." || virtual_name == "..")
416             continue;
417 
418         u64 ret_entries = 0;
419         if (!callback(&ret_entries, directory, virtual_name)) {
420             callback_error = true;
421             break;
422         }
423         found_entries += ret_entries;
424 
425 #ifdef _WIN32
426     } while (FindNextFileW(handle_find, &ffd) != 0);
427     FindClose(handle_find);
428 #else
429     }
430     closedir(dirp);
431 #endif
432 
433     if (callback_error)
434         return false;
435 
436     // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
437     if (num_entries_out != nullptr)
438         *num_entries_out = found_entries;
439     return true;
440 }
441 
ScanDirectoryTree(const std::string & directory,FSTEntry & parent_entry,unsigned int recursion)442 u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
443                       unsigned int recursion) {
444     const auto callback = [recursion, &parent_entry](u64* num_entries_out,
445                                                      const std::string& directory,
446                                                      const std::string& virtual_name) -> bool {
447         FSTEntry entry;
448         entry.virtualName = virtual_name;
449         entry.physicalName = directory + DIR_SEP + virtual_name;
450 
451         if (IsDirectory(entry.physicalName)) {
452             entry.isDirectory = true;
453             // is a directory, lets go inside if we didn't recurse to often
454             if (recursion > 0) {
455                 entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
456                 *num_entries_out += entry.size;
457             } else {
458                 entry.size = 0;
459             }
460         } else { // is a file
461             entry.isDirectory = false;
462             entry.size = GetSize(entry.physicalName);
463         }
464         (*num_entries_out)++;
465 
466         // Push into the tree
467         parent_entry.children.push_back(std::move(entry));
468         return true;
469     };
470 
471     u64 num_entries;
472     return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
473 }
474 
GetAllFilesFromNestedEntries(FSTEntry & directory,std::vector<FSTEntry> & output)475 void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output) {
476     std::vector<FSTEntry> files;
477     for (auto& entry : directory.children) {
478         if (entry.isDirectory) {
479             GetAllFilesFromNestedEntries(entry, output);
480         } else {
481             output.push_back(entry);
482         }
483     }
484 }
485 
DeleteDirRecursively(const std::string & directory,unsigned int recursion)486 bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
487     const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
488                                       const std::string& virtual_name) -> bool {
489         std::string new_path = directory + DIR_SEP_CHR + virtual_name;
490 
491         if (IsDirectory(new_path)) {
492             if (recursion == 0)
493                 return false;
494             return DeleteDirRecursively(new_path, recursion - 1);
495         }
496         return Delete(new_path);
497     };
498 
499     if (!ForeachDirectoryEntry(nullptr, directory, callback))
500         return false;
501 
502     // Delete the outermost directory
503     FileUtil::DeleteDir(directory);
504     return true;
505 }
506 
CopyDir(const std::string & source_path,const std::string & dest_path)507 void CopyDir(const std::string& source_path, const std::string& dest_path) {
508 #ifndef _WIN32
509     if (source_path == dest_path)
510         return;
511     if (!FileUtil::Exists(source_path))
512         return;
513     if (!FileUtil::Exists(dest_path))
514         FileUtil::CreateFullPath(dest_path);
515 
516     DIR* dirp = opendir(source_path.c_str());
517     if (!dirp)
518         return;
519 
520     while (struct dirent* result = readdir(dirp)) {
521         const std::string virtualName(result->d_name);
522         // check for "." and ".."
523         if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
524             ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
525             continue;
526 
527         std::string source, dest;
528         source = source_path + virtualName;
529         dest = dest_path + virtualName;
530         if (IsDirectory(source)) {
531             source += '/';
532             dest += '/';
533             if (!FileUtil::Exists(dest))
534                 FileUtil::CreateFullPath(dest);
535             CopyDir(source, dest);
536         } else if (!FileUtil::Exists(dest))
537             FileUtil::Copy(source, dest);
538     }
539     closedir(dirp);
540 #endif
541 }
542 
GetCurrentDir()543 std::optional<std::string> GetCurrentDir() {
544 // Get the current working directory (getcwd uses malloc)
545 #ifdef _WIN32
546     wchar_t* dir = _wgetcwd(nullptr, 0);
547     if (!dir) {
548 #else
549     char* dir = getcwd(nullptr, 0);
550     if (!dir) {
551 #endif
552         LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
553         return {};
554     }
555 #ifdef _WIN32
556     std::string strDir = Common::UTF16ToUTF8(dir);
557 #else
558     std::string strDir = dir;
559 #endif
560     free(dir);
561     return strDir;
562 } // namespace FileUtil
563 
564 bool SetCurrentDir(const std::string& directory) {
565 #ifdef _WIN32
566     return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
567 #else
568     return chdir(directory.c_str()) == 0;
569 #endif
570 }
571 
572 #if defined(__APPLE__)
573 std::string GetBundleDirectory() {
574     CFURLRef BundleRef;
575     char AppBundlePath[MAXPATHLEN];
576     // Get the main bundle for the app
577     BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
578     CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
579     CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
580     CFRelease(BundleRef);
581     CFRelease(BundlePath);
582 
583     return AppBundlePath;
584 }
585 #endif
586 
587 #ifdef _WIN32
588 const std::string& GetExeDirectory() {
589     static std::string exe_path;
590     if (exe_path.empty()) {
591         wchar_t wchar_exe_path[2048];
592         GetModuleFileNameW(nullptr, wchar_exe_path, 2048);
593         exe_path = Common::UTF16ToUTF8(wchar_exe_path);
594         exe_path = exe_path.substr(0, exe_path.find_last_of('\\'));
595     }
596     return exe_path;
597 }
598 
599 std::string AppDataRoamingDirectory() {
600     PWSTR pw_local_path = nullptr;
601     // Only supported by Windows Vista or later
602     SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path);
603     std::string local_path = Common::UTF16ToUTF8(pw_local_path);
604     CoTaskMemFree(pw_local_path);
605     return local_path;
606 }
607 #else
608 /**
609  * @return The user’s home directory on POSIX systems
610  */
611 static const std::string& GetHomeDirectory() {
612     static std::string home_path;
613     if (home_path.empty()) {
614         const char* envvar = getenv("HOME");
615         if (envvar) {
616             home_path = envvar;
617         } else {
618             auto pw = getpwuid(getuid());
619             ASSERT_MSG(pw,
620                        "$HOME isn’t defined, and the current user can’t be found in /etc/passwd.");
621             home_path = pw->pw_dir;
622         }
623     }
624     return home_path;
625 }
626 
627 /**
628  * Follows the XDG Base Directory Specification to get a directory path
629  * @param envvar The XDG environment variable to get the value from
630  * @return The directory path
631  * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
632  */
633 static const std::string GetUserDirectory(const std::string& envvar) {
634     const char* directory = getenv(envvar.c_str());
635 
636     std::string user_dir;
637     if (directory) {
638         user_dir = directory;
639     } else {
640         std::string subdirectory;
641         if (envvar == "XDG_DATA_HOME")
642             subdirectory = DIR_SEP ".local" DIR_SEP "share";
643         else if (envvar == "XDG_CONFIG_HOME")
644             subdirectory = DIR_SEP ".config";
645         else if (envvar == "XDG_CACHE_HOME")
646             subdirectory = DIR_SEP ".cache";
647         else
648             ASSERT_MSG(false, "Unknown XDG variable {}.", envvar);
649         user_dir = GetHomeDirectory() + subdirectory;
650     }
651 
652     ASSERT_MSG(!user_dir.empty(), "User directory {} musn’t be empty.", envvar);
653     ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar);
654 
655     return user_dir;
656 }
657 #endif
658 
659 std::string GetSysDirectory() {
660     std::string sysDir;
661 
662 #if defined(__APPLE__)
663     sysDir = GetBundleDirectory();
664     sysDir += DIR_SEP;
665     sysDir += SYSDATA_DIR;
666 #else
667     sysDir = SYSDATA_DIR;
668 #endif
669     sysDir += DIR_SEP;
670 
671     LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
672     return sysDir;
673 }
674 
675 namespace {
676 std::unordered_map<UserPath, std::string> g_paths;
677 }
678 
679 void SetUserPath(const std::string& path) {
680     std::string& user_path = g_paths[UserPath::UserDir];
681 
682     if (!path.empty() && CreateFullPath(path)) {
683         LOG_INFO(Common_Filesystem, "Using {} as the user directory", path);
684         user_path = path;
685         g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
686         g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
687     } else {
688 #ifdef _WIN32
689         user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
690         if (!FileUtil::IsDirectory(user_path)) {
691             user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
692         } else {
693             LOG_INFO(Common_Filesystem, "Using the local user directory");
694         }
695 
696         g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
697         g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
698 #elif ANDROID
699         if (FileUtil::Exists(DIR_SEP SDCARD_DIR)) {
700             user_path = DIR_SEP SDCARD_DIR DIR_SEP EMU_DATA_DIR DIR_SEP;
701             g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
702             g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
703         }
704 #else
705         if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
706             user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
707             g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
708             g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
709         } else {
710             std::string data_dir = GetUserDirectory("XDG_DATA_HOME");
711             std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME");
712             std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME");
713 
714             user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP;
715             g_paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
716             g_paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP);
717         }
718 #endif
719     }
720 
721     g_paths.emplace(UserPath::SDMCDir, !Settings::values.sdmc_dir.empty()
722                                            ? Settings::values.sdmc_dir
723                                            : user_path + SDMC_DIR DIR_SEP);
724     g_paths.emplace(UserPath::NANDDir, !Settings::values.nand_dir.empty()
725                                            ? Settings::values.nand_dir
726                                            : user_path + NAND_DIR DIR_SEP);
727     g_paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
728     // TODO: Put the logs in a better location for each OS
729     g_paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
730     g_paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP);
731     g_paths.emplace(UserPath::DLLDir, user_path + DLL_DIR DIR_SEP);
732     g_paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
733     g_paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
734     g_paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
735     g_paths.emplace(UserPath::StatesDir, user_path + STATES_DIR DIR_SEP);
736 }
737 
738 std::string g_currentRomPath{};
739 
740 void SetCurrentRomPath(const std::string& path) {
741     g_currentRomPath = path;
742 }
743 
744 bool StringReplace(std::string& haystack, const std::string& a, const std::string& b, bool swap) {
745     const auto& needle = swap ? b : a;
746     const auto& replacement = swap ? a : b;
747     if (needle.empty()) {
748         return false;
749     }
750     auto index = haystack.find(needle, 0);
751     if (index == std::string::npos) {
752         return false;
753     }
754     haystack.replace(index, needle.size(), replacement);
755     return true;
756 }
757 
758 std::string SerializePath(const std::string& input, bool is_saving) {
759     auto result = input;
760     StringReplace(result, "%CITRA_ROM_FILE%", g_currentRomPath, is_saving);
761     StringReplace(result, "%CITRA_USER_DIR%", GetUserPath(UserPath::UserDir), is_saving);
762     return result;
763 }
764 
765 const std::string& GetUserPath(UserPath path) {
766     // Set up all paths and files on the first run
767     if (g_paths.empty())
768         SetUserPath();
769     return g_paths[path];
770 }
771 
772 const void UpdateUserPath(UserPath path, const std::string& filename) {
773     g_paths[path] = filename + DIR_SEP;
774 }
775 
776 std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) {
777     return IOFile(filename, text_file ? "w" : "wb").WriteString(str);
778 }
779 
780 std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) {
781     IOFile file(filename, text_file ? "r" : "rb");
782 
783     if (!file.IsOpen())
784         return 0;
785 
786     str.resize(static_cast<u32>(file.GetSize()));
787     return file.ReadArray(str.data(), str.size());
788 }
789 
790 void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
791                      std::array<char, 4>& extension) {
792     const std::string forbidden_characters = ".\"/\\[]:;=, ";
793 
794     // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces.
795     short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}};
796     extension = {{' ', ' ', ' ', '\0'}};
797 
798     std::string::size_type point = filename.rfind('.');
799     if (point == filename.size() - 1)
800         point = filename.rfind('.', point);
801 
802     // Get short name.
803     int j = 0;
804     for (char letter : filename.substr(0, point)) {
805         if (forbidden_characters.find(letter, 0) != std::string::npos)
806             continue;
807         if (j == 8) {
808             // TODO(Link Mauve): also do that for filenames containing a space.
809             // TODO(Link Mauve): handle multiple files having the same short name.
810             short_name[6] = '~';
811             short_name[7] = '1';
812             break;
813         }
814         short_name[j++] = toupper(letter);
815     }
816 
817     // Get extension.
818     if (point != std::string::npos) {
819         j = 0;
820         for (char letter : filename.substr(point + 1, 3))
821             extension[j++] = toupper(letter);
822     }
823 }
824 
825 std::vector<std::string> SplitPathComponents(std::string_view filename) {
826     std::string copy(filename);
827     std::replace(copy.begin(), copy.end(), '\\', '/');
828     std::vector<std::string> out;
829 
830     std::stringstream stream(copy);
831     std::string item;
832     while (std::getline(stream, item, '/')) {
833         out.push_back(std::move(item));
834     }
835 
836     return out;
837 }
838 
839 std::string_view GetParentPath(std::string_view path) {
840     const auto name_bck_index = path.rfind('\\');
841     const auto name_fwd_index = path.rfind('/');
842     std::size_t name_index;
843 
844     if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
845         name_index = std::min(name_bck_index, name_fwd_index);
846     } else {
847         name_index = std::max(name_bck_index, name_fwd_index);
848     }
849 
850     return path.substr(0, name_index);
851 }
852 
853 std::string_view GetPathWithoutTop(std::string_view path) {
854     if (path.empty()) {
855         return path;
856     }
857 
858     while (path[0] == '\\' || path[0] == '/') {
859         path.remove_prefix(1);
860         if (path.empty()) {
861             return path;
862         }
863     }
864 
865     const auto name_bck_index = path.find('\\');
866     const auto name_fwd_index = path.find('/');
867     return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
868 }
869 
870 std::string_view GetFilename(std::string_view path) {
871     const auto name_index = path.find_last_of("\\/");
872 
873     if (name_index == std::string_view::npos) {
874         return {};
875     }
876 
877     return path.substr(name_index + 1);
878 }
879 
880 std::string_view GetExtensionFromFilename(std::string_view name) {
881     const std::size_t index = name.rfind('.');
882 
883     if (index == std::string_view::npos) {
884         return {};
885     }
886 
887     return name.substr(index + 1);
888 }
889 
890 std::string_view RemoveTrailingSlash(std::string_view path) {
891     if (path.empty()) {
892         return path;
893     }
894 
895     if (path.back() == '\\' || path.back() == '/') {
896         path.remove_suffix(1);
897         return path;
898     }
899 
900     return path;
901 }
902 
903 std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
904     std::string path(path_);
905     char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
906     char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
907 
908     if (directory_separator == DirectorySeparator::PlatformDefault) {
909 #ifdef _WIN32
910         type1 = '/';
911         type2 = '\\';
912 #endif
913     }
914 
915     std::replace(path.begin(), path.end(), type1, type2);
916 
917     auto start = path.begin();
918 #ifdef _WIN32
919     // allow network paths which start with a double backslash (e.g. \\server\share)
920     if (start != path.end())
921         ++start;
922 #endif
923     path.erase(std::unique(start, path.end(),
924                            [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
925                path.end());
926     return std::string(RemoveTrailingSlash(path));
927 }
928 
929 IOFile::IOFile() = default;
930 
931 IOFile::IOFile(const std::string& filename, const char openmode[], int flags)
932     : filename(filename), openmode(openmode), flags(flags) {
933     Open();
934 }
935 
936 IOFile::~IOFile() {
937     Close();
938 }
939 
940 IOFile::IOFile(IOFile&& other) noexcept {
941     Swap(other);
942 }
943 
944 IOFile& IOFile::operator=(IOFile&& other) noexcept {
945     Swap(other);
946     return *this;
947 }
948 
949 void IOFile::Swap(IOFile& other) noexcept {
950     std::swap(m_file, other.m_file);
951     std::swap(m_good, other.m_good);
952     std::swap(filename, other.filename);
953     std::swap(openmode, other.openmode);
954     std::swap(flags, other.flags);
955 }
956 
957 bool IOFile::Open() {
958     Close();
959 
960 #ifdef _WIN32
961     if (flags != 0) {
962         m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
963                           Common::UTF8ToUTF16W(openmode).c_str(), flags);
964         m_good = m_file != nullptr;
965     } else {
966         m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
967                            Common::UTF8ToUTF16W(openmode).c_str()) == 0;
968     }
969 #else
970     m_file = std::fopen(filename.c_str(), openmode.c_str());
971     m_good = m_file != nullptr;
972 #endif
973 
974     return m_good;
975 }
976 
977 bool IOFile::Close() {
978     if (!IsOpen() || 0 != std::fclose(m_file))
979         m_good = false;
980 
981     m_file = nullptr;
982     return m_good;
983 }
984 
985 u64 IOFile::GetSize() const {
986     if (IsOpen())
987         return FileUtil::GetSize(m_file);
988 
989     return 0;
990 }
991 
992 bool IOFile::Seek(s64 off, int origin) {
993     if (!IsOpen() || 0 != fseeko(m_file, off, origin))
994         m_good = false;
995 
996     return m_good;
997 }
998 
999 u64 IOFile::Tell() const {
1000     if (IsOpen())
1001         return ftello(m_file);
1002 
1003     return std::numeric_limits<u64>::max();
1004 }
1005 
1006 bool IOFile::Flush() {
1007     if (!IsOpen() || 0 != std::fflush(m_file))
1008         m_good = false;
1009 
1010     return m_good;
1011 }
1012 
1013 std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) {
1014     if (!IsOpen()) {
1015         m_good = false;
1016         return std::numeric_limits<std::size_t>::max();
1017     }
1018 
1019     if (length == 0) {
1020         return 0;
1021     }
1022 
1023     DEBUG_ASSERT(data != nullptr);
1024 
1025     return std::fread(data, data_size, length, m_file);
1026 }
1027 
1028 std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
1029     if (!IsOpen()) {
1030         m_good = false;
1031         return std::numeric_limits<std::size_t>::max();
1032     }
1033 
1034     if (length == 0) {
1035         return 0;
1036     }
1037 
1038     DEBUG_ASSERT(data != nullptr);
1039 
1040     return std::fwrite(data, data_size, length, m_file);
1041 }
1042 
1043 bool IOFile::Resize(u64 size) {
1044     if (!IsOpen() || 0 !=
1045 #ifdef _WIN32
1046                          // ector: _chsize sucks, not 64-bit safe
1047                          // F|RES: changed to _chsize_s. i think it is 64-bit safe
1048                          _chsize_s(_fileno(m_file), size)
1049 #else
1050                          // TODO: handle 64bit and growing
1051                          ftruncate(fileno(m_file), size)
1052 #endif
1053     )
1054         m_good = false;
1055 
1056     return m_good;
1057 }
1058 
1059 } // namespace FileUtil
1060