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