/ src / common / file_util.cpp
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