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