file_util.cpp 28 KB

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