file_util.cpp 30 KB

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