file_util.cpp 30 KB

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