diff options
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | src/common/common_paths.h | 52 | ||||
| -rw-r--r-- | src/common/file_util.cpp | 1032 | ||||
| -rw-r--r-- | src/common/file_util.h | 298 | ||||
| -rw-r--r-- | src/common/fs/file.cpp | 392 | ||||
| -rw-r--r-- | src/common/fs/file.h | 450 | ||||
| -rw-r--r-- | src/common/fs/fs.cpp | 610 | ||||
| -rw-r--r-- | src/common/fs/fs.h | 582 | ||||
| -rw-r--r-- | src/common/fs/fs_paths.h | 27 | ||||
| -rw-r--r-- | src/common/fs/fs_types.h | 73 | ||||
| -rw-r--r-- | src/common/fs/fs_util.cpp | 13 | ||||
| -rw-r--r-- | src/common/fs/fs_util.h | 25 | ||||
| -rw-r--r-- | src/common/fs/path_util.cpp | 432 | ||||
| -rw-r--r-- | src/common/fs/path_util.h | 309 | ||||
| -rw-r--r-- | src/common/logging/backend.cpp | 27 | ||||
| -rw-r--r-- | src/common/logging/backend.h | 5 | ||||
| -rw-r--r-- | src/common/nvidia_flags.cpp | 24 | ||||
| -rw-r--r-- | src/common/settings.cpp | 16 | ||||
| -rw-r--r-- | src/common/string_util.cpp | 13 | ||||
| -rw-r--r-- | src/common/string_util.h | 2 |
20 files changed, 2963 insertions, 1432 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 88644eeb6..eafb96b0b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -109,7 +109,6 @@ add_library(common STATIC | |||
| 109 | cityhash.cpp | 109 | cityhash.cpp |
| 110 | cityhash.h | 110 | cityhash.h |
| 111 | common_funcs.h | 111 | common_funcs.h |
| 112 | common_paths.h | ||
| 113 | common_sizes.h | 112 | common_sizes.h |
| 114 | common_types.h | 113 | common_types.h |
| 115 | concepts.h | 114 | concepts.h |
| @@ -118,8 +117,16 @@ add_library(common STATIC | |||
| 118 | dynamic_library.h | 117 | dynamic_library.h |
| 119 | fiber.cpp | 118 | fiber.cpp |
| 120 | fiber.h | 119 | fiber.h |
| 121 | file_util.cpp | 120 | fs/file.cpp |
| 122 | file_util.h | 121 | fs/file.h |
| 122 | fs/fs.cpp | ||
| 123 | fs/fs.h | ||
| 124 | fs/fs_paths.h | ||
| 125 | fs/fs_types.h | ||
| 126 | fs/fs_util.cpp | ||
| 127 | fs/fs_util.h | ||
| 128 | fs/path_util.cpp | ||
| 129 | fs/path_util.h | ||
| 123 | hash.h | 130 | hash.h |
| 124 | hex_util.cpp | 131 | hex_util.cpp |
| 125 | hex_util.h | 132 | hex_util.h |
diff --git a/src/common/common_paths.h b/src/common/common_paths.h deleted file mode 100644 index 3c593d5f6..000000000 --- a/src/common/common_paths.h +++ /dev/null | |||
| @@ -1,52 +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 | #pragma once | ||
| 6 | |||
| 7 | // Directory separators, do we need this? | ||
| 8 | #define DIR_SEP "/" | ||
| 9 | #define DIR_SEP_CHR '/' | ||
| 10 | |||
| 11 | #ifndef MAX_PATH | ||
| 12 | #define MAX_PATH 260 | ||
| 13 | #endif | ||
| 14 | |||
| 15 | // The user data dir | ||
| 16 | #define ROOT_DIR "." | ||
| 17 | #define USERDATA_DIR "user" | ||
| 18 | #ifdef USER_DIR | ||
| 19 | #define EMU_DATA_DIR USER_DIR | ||
| 20 | #else | ||
| 21 | #define EMU_DATA_DIR "yuzu" | ||
| 22 | #endif | ||
| 23 | |||
| 24 | // Dirs in both User and Sys | ||
| 25 | #define EUR_DIR "EUR" | ||
| 26 | #define USA_DIR "USA" | ||
| 27 | #define JAP_DIR "JAP" | ||
| 28 | |||
| 29 | // Subdirs in the User dir returned by GetUserPath(UserPath::UserDir) | ||
| 30 | #define CONFIG_DIR "config" | ||
| 31 | #define CACHE_DIR "cache" | ||
| 32 | #define SDMC_DIR "sdmc" | ||
| 33 | #define NAND_DIR "nand" | ||
| 34 | #define SYSDATA_DIR "sysdata" | ||
| 35 | #define KEYS_DIR "keys" | ||
| 36 | #define LOAD_DIR "load" | ||
| 37 | #define DUMP_DIR "dump" | ||
| 38 | #define SCREENSHOTS_DIR "screenshots" | ||
| 39 | #define SHADER_DIR "shader" | ||
| 40 | #define LOG_DIR "log" | ||
| 41 | |||
| 42 | // Filenames | ||
| 43 | // Files in the directory returned by GetUserPath(UserPath::ConfigDir) | ||
| 44 | #define EMU_CONFIG "emu.ini" | ||
| 45 | #define DEBUGGER_CONFIG "debugger.ini" | ||
| 46 | #define LOGGER_CONFIG "logger.ini" | ||
| 47 | // Files in the directory returned by GetUserPath(UserPath::LogDir) | ||
| 48 | #define LOG_FILE "yuzu_log.txt" | ||
| 49 | |||
| 50 | // Sys files | ||
| 51 | #define SHARED_FONT "shared_font.bin" | ||
| 52 | #define AES_KEYS "aes_keys.txt" | ||
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 | ||
diff --git a/src/common/file_util.h b/src/common/file_util.h deleted file mode 100644 index 840cde2a6..000000000 --- a/src/common/file_util.h +++ /dev/null | |||
| @@ -1,298 +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 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <cstdio> | ||
| 9 | #include <fstream> | ||
| 10 | #include <functional> | ||
| 11 | #include <limits> | ||
| 12 | #include <optional> | ||
| 13 | #include <string> | ||
| 14 | #include <string_view> | ||
| 15 | #include <type_traits> | ||
| 16 | #include <vector> | ||
| 17 | #include "common/common_types.h" | ||
| 18 | #ifdef _MSC_VER | ||
| 19 | #include "common/string_util.h" | ||
| 20 | #endif | ||
| 21 | |||
| 22 | namespace Common::FS { | ||
| 23 | |||
| 24 | // User paths for GetUserPath | ||
| 25 | enum class UserPath { | ||
| 26 | CacheDir, | ||
| 27 | ConfigDir, | ||
| 28 | KeysDir, | ||
| 29 | LogDir, | ||
| 30 | NANDDir, | ||
| 31 | RootDir, | ||
| 32 | SDMCDir, | ||
| 33 | LoadDir, | ||
| 34 | DumpDir, | ||
| 35 | ScreenshotsDir, | ||
| 36 | ShaderDir, | ||
| 37 | SysDataDir, | ||
| 38 | UserDir, | ||
| 39 | }; | ||
| 40 | |||
| 41 | // FileSystem tree node/ | ||
| 42 | struct FSTEntry { | ||
| 43 | bool isDirectory; | ||
| 44 | u64 size; // file length or number of entries from children | ||
| 45 | std::string physicalName; // name on disk | ||
| 46 | std::string virtualName; // name in FST names table | ||
| 47 | std::vector<FSTEntry> children; | ||
| 48 | }; | ||
| 49 | |||
| 50 | // Returns true if file filename exists | ||
| 51 | [[nodiscard]] bool Exists(const std::string& filename); | ||
| 52 | |||
| 53 | // Returns true if filename is a directory | ||
| 54 | [[nodiscard]] bool IsDirectory(const std::string& filename); | ||
| 55 | |||
| 56 | // Returns the size of filename (64bit) | ||
| 57 | [[nodiscard]] u64 GetSize(const std::string& filename); | ||
| 58 | |||
| 59 | // Overloaded GetSize, accepts file descriptor | ||
| 60 | [[nodiscard]] u64 GetSize(int fd); | ||
| 61 | |||
| 62 | // Overloaded GetSize, accepts FILE* | ||
| 63 | [[nodiscard]] u64 GetSize(FILE* f); | ||
| 64 | |||
| 65 | // Returns true if successful, or path already exists. | ||
| 66 | bool CreateDir(const std::string& filename); | ||
| 67 | |||
| 68 | // Creates the full path of fullPath returns true on success | ||
| 69 | bool CreateFullPath(const std::string& fullPath); | ||
| 70 | |||
| 71 | // Deletes a given filename, return true on success | ||
| 72 | // Doesn't supports deleting a directory | ||
| 73 | bool Delete(const std::string& filename); | ||
| 74 | |||
| 75 | // Deletes a directory filename, returns true on success | ||
| 76 | bool DeleteDir(const std::string& filename); | ||
| 77 | |||
| 78 | // renames file srcFilename to destFilename, returns true on success | ||
| 79 | bool Rename(const std::string& srcFilename, const std::string& destFilename); | ||
| 80 | |||
| 81 | // copies file srcFilename to destFilename, returns true on success | ||
| 82 | bool Copy(const std::string& srcFilename, const std::string& destFilename); | ||
| 83 | |||
| 84 | // creates an empty file filename, returns true on success | ||
| 85 | bool CreateEmptyFile(const std::string& filename); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * @param num_entries_out to be assigned by the callable with the number of iterated directory | ||
| 89 | * entries, never null | ||
| 90 | * @param directory the path to the enclosing directory | ||
| 91 | * @param virtual_name the entry name, without any preceding directory info | ||
| 92 | * @return whether handling the entry succeeded | ||
| 93 | */ | ||
| 94 | using DirectoryEntryCallable = std::function<bool( | ||
| 95 | u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Scans a directory, calling the callback for each file/directory contained within. | ||
| 99 | * If the callback returns failure, scanning halts and this function returns failure as well | ||
| 100 | * @param num_entries_out assigned by the function with the number of iterated directory entries, | ||
| 101 | * can be null | ||
| 102 | * @param directory the directory to scan | ||
| 103 | * @param callback The callback which will be called for each entry | ||
| 104 | * @return whether scanning the directory succeeded | ||
| 105 | */ | ||
| 106 | bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, | ||
| 107 | DirectoryEntryCallable callback); | ||
| 108 | |||
| 109 | /** | ||
| 110 | * Scans the directory tree, storing the results. | ||
| 111 | * @param directory the parent directory to start scanning from | ||
| 112 | * @param parent_entry FSTEntry where the filesystem tree results will be stored. | ||
| 113 | * @param recursion Number of children directories to read before giving up. | ||
| 114 | * @return the total number of files/directories found | ||
| 115 | */ | ||
| 116 | u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, | ||
| 117 | unsigned int recursion = 0); | ||
| 118 | |||
| 119 | // deletes the given directory and anything under it. Returns true on success. | ||
| 120 | bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); | ||
| 121 | |||
| 122 | // Returns the current directory | ||
| 123 | [[nodiscard]] std::optional<std::string> GetCurrentDir(); | ||
| 124 | |||
| 125 | // Create directory and copy contents (does not overwrite existing files) | ||
| 126 | void CopyDir(const std::string& source_path, const std::string& dest_path); | ||
| 127 | |||
| 128 | // Set the current directory to given directory | ||
| 129 | bool SetCurrentDir(const std::string& directory); | ||
| 130 | |||
| 131 | // Returns a pointer to a string with a yuzu data dir in the user's home | ||
| 132 | // directory. To be used in "multi-user" mode (that is, installed). | ||
| 133 | const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); | ||
| 134 | |||
| 135 | [[nodiscard]] std::string GetHactoolConfigurationPath(); | ||
| 136 | |||
| 137 | [[nodiscard]] std::string GetNANDRegistrationDir(bool system = false); | ||
| 138 | |||
| 139 | // Returns the path to where the sys file are | ||
| 140 | [[nodiscard]] std::string GetSysDirectory(); | ||
| 141 | |||
| 142 | #ifdef __APPLE__ | ||
| 143 | [[nodiscard]] std::string GetBundleDirectory(); | ||
| 144 | #endif | ||
| 145 | |||
| 146 | #ifdef _WIN32 | ||
| 147 | [[nodiscard]] const std::string& GetExeDirectory(); | ||
| 148 | [[nodiscard]] std::string AppDataRoamingDirectory(); | ||
| 149 | #endif | ||
| 150 | |||
| 151 | std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str); | ||
| 152 | |||
| 153 | std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str); | ||
| 154 | |||
| 155 | /** | ||
| 156 | * Splits the filename into 8.3 format | ||
| 157 | * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename | ||
| 158 | * @param filename The normal filename to use | ||
| 159 | * @param short_name A 9-char array in which the short name will be written | ||
| 160 | * @param extension A 4-char array in which the extension will be written | ||
| 161 | */ | ||
| 162 | void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, | ||
| 163 | std::array<char, 4>& extension); | ||
| 164 | |||
| 165 | // Splits the path on '/' or '\' and put the components into a vector | ||
| 166 | // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } | ||
| 167 | [[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); | ||
| 168 | |||
| 169 | // Gets all of the text up to the last '/' or '\' in the path. | ||
| 170 | [[nodiscard]] std::string_view GetParentPath(std::string_view path); | ||
| 171 | |||
| 172 | // Gets all of the text after the first '/' or '\' in the path. | ||
| 173 | [[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); | ||
| 174 | |||
| 175 | // Gets the filename of the path | ||
| 176 | [[nodiscard]] std::string_view GetFilename(std::string_view path); | ||
| 177 | |||
| 178 | // Gets the extension of the filename | ||
| 179 | [[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); | ||
| 180 | |||
| 181 | // Removes the final '/' or '\' if one exists | ||
| 182 | [[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); | ||
| 183 | |||
| 184 | // Creates a new vector containing indices [first, last) from the original. | ||
| 185 | template <typename T> | ||
| 186 | [[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, | ||
| 187 | std::size_t last) { | ||
| 188 | if (first >= last) { | ||
| 189 | return {}; | ||
| 190 | } | ||
| 191 | last = std::min<std::size_t>(last, vector.size()); | ||
| 192 | return std::vector<T>(vector.begin() + first, vector.begin() + first + last); | ||
| 193 | } | ||
| 194 | |||
| 195 | enum class DirectorySeparator { | ||
| 196 | ForwardSlash, | ||
| 197 | BackwardSlash, | ||
| 198 | PlatformDefault, | ||
| 199 | }; | ||
| 200 | |||
| 201 | // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' | ||
| 202 | // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows | ||
| 203 | [[nodiscard]] std::string SanitizePath( | ||
| 204 | std::string_view path, | ||
| 205 | DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); | ||
| 206 | |||
| 207 | // To deal with Windows being dumb at Unicode | ||
| 208 | template <typename T> | ||
| 209 | void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { | ||
| 210 | #ifdef _MSC_VER | ||
| 211 | fstream.open(Common::UTF8ToUTF16W(filename), openmode); | ||
| 212 | #else | ||
| 213 | fstream.open(filename, openmode); | ||
| 214 | #endif | ||
| 215 | } | ||
| 216 | |||
| 217 | // simple wrapper for cstdlib file functions to | ||
| 218 | // hopefully will make error checking easier | ||
| 219 | // and make forgetting an fclose() harder | ||
| 220 | class IOFile : public NonCopyable { | ||
| 221 | public: | ||
| 222 | IOFile(); | ||
| 223 | // flags is used for windows specific file open mode flags, which | ||
| 224 | // allows yuzu to open the logs in shared write mode, so that the file | ||
| 225 | // isn't considered "locked" while yuzu is open and people can open the log file and view it | ||
| 226 | IOFile(const std::string& filename, const char openmode[], int flags = 0); | ||
| 227 | |||
| 228 | ~IOFile(); | ||
| 229 | |||
| 230 | IOFile(IOFile&& other) noexcept; | ||
| 231 | IOFile& operator=(IOFile&& other) noexcept; | ||
| 232 | |||
| 233 | void Swap(IOFile& other) noexcept; | ||
| 234 | |||
| 235 | bool Open(const std::string& filename, const char openmode[], int flags = 0); | ||
| 236 | bool Close(); | ||
| 237 | |||
| 238 | template <typename T> | ||
| 239 | std::size_t ReadArray(T* data, std::size_t length) const { | ||
| 240 | static_assert(std::is_trivially_copyable_v<T>, | ||
| 241 | "Given array does not consist of trivially copyable objects"); | ||
| 242 | |||
| 243 | return ReadImpl(data, length, sizeof(T)); | ||
| 244 | } | ||
| 245 | |||
| 246 | template <typename T> | ||
| 247 | std::size_t WriteArray(const T* data, std::size_t length) { | ||
| 248 | static_assert(std::is_trivially_copyable_v<T>, | ||
| 249 | "Given array does not consist of trivially copyable objects"); | ||
| 250 | |||
| 251 | return WriteImpl(data, length, sizeof(T)); | ||
| 252 | } | ||
| 253 | |||
| 254 | template <typename T> | ||
| 255 | std::size_t ReadBytes(T* data, std::size_t length) const { | ||
| 256 | static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); | ||
| 257 | return ReadArray(reinterpret_cast<char*>(data), length); | ||
| 258 | } | ||
| 259 | |||
| 260 | template <typename T> | ||
| 261 | std::size_t WriteBytes(const T* data, std::size_t length) { | ||
| 262 | static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); | ||
| 263 | return WriteArray(reinterpret_cast<const char*>(data), length); | ||
| 264 | } | ||
| 265 | |||
| 266 | template <typename T> | ||
| 267 | std::size_t WriteObject(const T& object) { | ||
| 268 | static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer"); | ||
| 269 | return WriteArray(&object, 1); | ||
| 270 | } | ||
| 271 | |||
| 272 | std::size_t WriteString(std::string_view str) { | ||
| 273 | return WriteArray(str.data(), str.length()); | ||
| 274 | } | ||
| 275 | |||
| 276 | [[nodiscard]] bool IsOpen() const { | ||
| 277 | return nullptr != m_file; | ||
| 278 | } | ||
| 279 | |||
| 280 | bool Seek(s64 off, int origin) const; | ||
| 281 | [[nodiscard]] u64 Tell() const; | ||
| 282 | [[nodiscard]] u64 GetSize() const; | ||
| 283 | bool Resize(u64 size); | ||
| 284 | bool Flush(); | ||
| 285 | |||
| 286 | // clear error state | ||
| 287 | void Clear() { | ||
| 288 | std::clearerr(m_file); | ||
| 289 | } | ||
| 290 | |||
| 291 | private: | ||
| 292 | std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const; | ||
| 293 | std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size); | ||
| 294 | |||
| 295 | std::FILE* m_file = nullptr; | ||
| 296 | }; | ||
| 297 | |||
| 298 | } // namespace Common::FS | ||
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp new file mode 100644 index 000000000..9f3de1cb0 --- /dev/null +++ b/src/common/fs/file.cpp | |||
| @@ -0,0 +1,392 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | |||
| 10 | #ifdef _WIN32 | ||
| 11 | #include <io.h> | ||
| 12 | #include <share.h> | ||
| 13 | #else | ||
| 14 | #include <unistd.h> | ||
| 15 | #endif | ||
| 16 | |||
| 17 | #ifdef _MSC_VER | ||
| 18 | #define fileno _fileno | ||
| 19 | #define fseeko _fseeki64 | ||
| 20 | #define ftello _ftelli64 | ||
| 21 | #endif | ||
| 22 | |||
| 23 | namespace Common::FS { | ||
| 24 | |||
| 25 | namespace fs = std::filesystem; | ||
| 26 | |||
| 27 | namespace { | ||
| 28 | |||
| 29 | #ifdef _WIN32 | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Converts the file access mode and file type enums to a file access mode wide string. | ||
| 33 | * | ||
| 34 | * @param mode File access mode | ||
| 35 | * @param type File type | ||
| 36 | * | ||
| 37 | * @returns A pointer to a wide string representing the file access mode. | ||
| 38 | */ | ||
| 39 | [[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) { | ||
| 40 | switch (type) { | ||
| 41 | case FileType::BinaryFile: | ||
| 42 | switch (mode) { | ||
| 43 | case FileAccessMode::Read: | ||
| 44 | return L"rb"; | ||
| 45 | case FileAccessMode::Write: | ||
| 46 | return L"wb"; | ||
| 47 | case FileAccessMode::Append: | ||
| 48 | return L"ab"; | ||
| 49 | case FileAccessMode::ReadWrite: | ||
| 50 | return L"r+b"; | ||
| 51 | case FileAccessMode::ReadAppend: | ||
| 52 | return L"a+b"; | ||
| 53 | } | ||
| 54 | break; | ||
| 55 | case FileType::TextFile: | ||
| 56 | switch (mode) { | ||
| 57 | case FileAccessMode::Read: | ||
| 58 | return L"r"; | ||
| 59 | case FileAccessMode::Write: | ||
| 60 | return L"w"; | ||
| 61 | case FileAccessMode::Append: | ||
| 62 | return L"a"; | ||
| 63 | case FileAccessMode::ReadWrite: | ||
| 64 | return L"r+"; | ||
| 65 | case FileAccessMode::ReadAppend: | ||
| 66 | return L"a+"; | ||
| 67 | } | ||
| 68 | break; | ||
| 69 | } | ||
| 70 | |||
| 71 | return L""; | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Converts the file-share access flag enum to a Windows defined file-share access flag. | ||
| 76 | * | ||
| 77 | * @param flag File-share access flag | ||
| 78 | * | ||
| 79 | * @returns Windows defined file-share access flag. | ||
| 80 | */ | ||
| 81 | [[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) { | ||
| 82 | switch (flag) { | ||
| 83 | case FileShareFlag::ShareNone: | ||
| 84 | default: | ||
| 85 | return _SH_DENYRW; | ||
| 86 | case FileShareFlag::ShareReadOnly: | ||
| 87 | return _SH_DENYWR; | ||
| 88 | case FileShareFlag::ShareWriteOnly: | ||
| 89 | return _SH_DENYRD; | ||
| 90 | case FileShareFlag::ShareReadWrite: | ||
| 91 | return _SH_DENYNO; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | #else | ||
| 96 | |||
| 97 | /** | ||
| 98 | * Converts the file access mode and file type enums to a file access mode string. | ||
| 99 | * | ||
| 100 | * @param mode File access mode | ||
| 101 | * @param type File type | ||
| 102 | * | ||
| 103 | * @returns A pointer to a string representing the file access mode. | ||
| 104 | */ | ||
| 105 | [[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) { | ||
| 106 | switch (type) { | ||
| 107 | case FileType::BinaryFile: | ||
| 108 | switch (mode) { | ||
| 109 | case FileAccessMode::Read: | ||
| 110 | return "rb"; | ||
| 111 | case FileAccessMode::Write: | ||
| 112 | return "wb"; | ||
| 113 | case FileAccessMode::Append: | ||
| 114 | return "ab"; | ||
| 115 | case FileAccessMode::ReadWrite: | ||
| 116 | return "r+b"; | ||
| 117 | case FileAccessMode::ReadAppend: | ||
| 118 | return "a+b"; | ||
| 119 | } | ||
| 120 | break; | ||
| 121 | case FileType::TextFile: | ||
| 122 | switch (mode) { | ||
| 123 | case FileAccessMode::Read: | ||
| 124 | return "r"; | ||
| 125 | case FileAccessMode::Write: | ||
| 126 | return "w"; | ||
| 127 | case FileAccessMode::Append: | ||
| 128 | return "a"; | ||
| 129 | case FileAccessMode::ReadWrite: | ||
| 130 | return "r+"; | ||
| 131 | case FileAccessMode::ReadAppend: | ||
| 132 | return "a+"; | ||
| 133 | } | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | |||
| 137 | return ""; | ||
| 138 | } | ||
| 139 | |||
| 140 | #endif | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Converts the seek origin enum to a seek origin integer. | ||
| 144 | * | ||
| 145 | * @param origin Seek origin | ||
| 146 | * | ||
| 147 | * @returns Seek origin integer. | ||
| 148 | */ | ||
| 149 | [[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { | ||
| 150 | switch (origin) { | ||
| 151 | case SeekOrigin::SetOrigin: | ||
| 152 | default: | ||
| 153 | return SEEK_SET; | ||
| 154 | case SeekOrigin::CurrentPosition: | ||
| 155 | return SEEK_CUR; | ||
| 156 | case SeekOrigin::End: | ||
| 157 | return SEEK_END; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | } // Anonymous namespace | ||
| 162 | |||
| 163 | std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) { | ||
| 164 | if (!IsFile(path)) { | ||
| 165 | return ""; | ||
| 166 | } | ||
| 167 | |||
| 168 | IOFile io_file{path, FileAccessMode::Read, type}; | ||
| 169 | |||
| 170 | return io_file.ReadString(io_file.GetSize()); | ||
| 171 | } | ||
| 172 | |||
| 173 | size_t WriteStringToFile(const std::filesystem::path& path, FileType type, | ||
| 174 | std::string_view string) { | ||
| 175 | if (!IsFile(path)) { | ||
| 176 | return 0; | ||
| 177 | } | ||
| 178 | |||
| 179 | IOFile io_file{path, FileAccessMode::Write, type}; | ||
| 180 | |||
| 181 | return io_file.WriteString(string); | ||
| 182 | } | ||
| 183 | |||
| 184 | size_t AppendStringToFile(const std::filesystem::path& path, FileType type, | ||
| 185 | std::string_view string) { | ||
| 186 | if (!Exists(path)) { | ||
| 187 | return WriteStringToFile(path, type, string); | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!IsFile(path)) { | ||
| 191 | return 0; | ||
| 192 | } | ||
| 193 | |||
| 194 | IOFile io_file{path, FileAccessMode::Append, type}; | ||
| 195 | |||
| 196 | return io_file.WriteString(string); | ||
| 197 | } | ||
| 198 | |||
| 199 | IOFile::IOFile() = default; | ||
| 200 | |||
| 201 | IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 202 | Open(path, mode, type, flag); | ||
| 203 | } | ||
| 204 | |||
| 205 | IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 206 | Open(path, mode, type, flag); | ||
| 207 | } | ||
| 208 | |||
| 209 | IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 210 | Open(path, mode, type, flag); | ||
| 211 | } | ||
| 212 | |||
| 213 | IOFile::~IOFile() { | ||
| 214 | Close(); | ||
| 215 | } | ||
| 216 | |||
| 217 | IOFile::IOFile(IOFile&& other) noexcept { | ||
| 218 | std::swap(file_path, other.file_path); | ||
| 219 | std::swap(file_access_mode, other.file_access_mode); | ||
| 220 | std::swap(file_type, other.file_type); | ||
| 221 | std::swap(file, other.file); | ||
| 222 | } | ||
| 223 | |||
| 224 | IOFile& IOFile::operator=(IOFile&& other) noexcept { | ||
| 225 | std::swap(file_path, other.file_path); | ||
| 226 | std::swap(file_access_mode, other.file_access_mode); | ||
| 227 | std::swap(file_type, other.file_type); | ||
| 228 | std::swap(file, other.file); | ||
| 229 | return *this; | ||
| 230 | } | ||
| 231 | |||
| 232 | fs::path IOFile::GetPath() const { | ||
| 233 | return file_path; | ||
| 234 | } | ||
| 235 | |||
| 236 | FileAccessMode IOFile::GetAccessMode() const { | ||
| 237 | return file_access_mode; | ||
| 238 | } | ||
| 239 | |||
| 240 | FileType IOFile::GetType() const { | ||
| 241 | return file_type; | ||
| 242 | } | ||
| 243 | |||
| 244 | void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { | ||
| 245 | Close(); | ||
| 246 | |||
| 247 | file_path = path; | ||
| 248 | file_access_mode = mode; | ||
| 249 | file_type = type; | ||
| 250 | |||
| 251 | errno = 0; | ||
| 252 | |||
| 253 | #ifdef _WIN32 | ||
| 254 | if (flag != FileShareFlag::ShareNone) { | ||
| 255 | file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); | ||
| 256 | } else { | ||
| 257 | _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); | ||
| 258 | } | ||
| 259 | #else | ||
| 260 | file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); | ||
| 261 | #endif | ||
| 262 | |||
| 263 | if (!IsOpen()) { | ||
| 264 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 265 | LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}", | ||
| 266 | PathToUTF8String(file_path), ec.message()); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | void IOFile::Close() { | ||
| 271 | if (!IsOpen()) { | ||
| 272 | return; | ||
| 273 | } | ||
| 274 | |||
| 275 | errno = 0; | ||
| 276 | |||
| 277 | const auto close_result = std::fclose(file) == 0; | ||
| 278 | |||
| 279 | if (!close_result) { | ||
| 280 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 281 | LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}", | ||
| 282 | PathToUTF8String(file_path), ec.message()); | ||
| 283 | } | ||
| 284 | |||
| 285 | file = nullptr; | ||
| 286 | } | ||
| 287 | |||
| 288 | bool IOFile::IsOpen() const { | ||
| 289 | return file != nullptr; | ||
| 290 | } | ||
| 291 | |||
| 292 | std::string IOFile::ReadString(size_t length) const { | ||
| 293 | std::vector<char> string_buffer(length); | ||
| 294 | |||
| 295 | const auto chars_read = ReadSpan<char>(string_buffer); | ||
| 296 | const auto string_size = chars_read != length ? chars_read : length; | ||
| 297 | |||
| 298 | return std::string{string_buffer.data(), string_size}; | ||
| 299 | } | ||
| 300 | |||
| 301 | size_t IOFile::WriteString(std::span<const char> string) const { | ||
| 302 | return WriteSpan(string); | ||
| 303 | } | ||
| 304 | |||
| 305 | bool IOFile::Flush() const { | ||
| 306 | if (!IsOpen()) { | ||
| 307 | return false; | ||
| 308 | } | ||
| 309 | |||
| 310 | errno = 0; | ||
| 311 | |||
| 312 | const auto flush_result = std::fflush(file) == 0; | ||
| 313 | |||
| 314 | if (!flush_result) { | ||
| 315 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 316 | LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}", | ||
| 317 | PathToUTF8String(file_path), ec.message()); | ||
| 318 | } | ||
| 319 | |||
| 320 | return flush_result; | ||
| 321 | } | ||
| 322 | |||
| 323 | bool IOFile::SetSize(u64 size) const { | ||
| 324 | if (!IsOpen()) { | ||
| 325 | return false; | ||
| 326 | } | ||
| 327 | |||
| 328 | errno = 0; | ||
| 329 | |||
| 330 | #ifdef _WIN32 | ||
| 331 | const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0; | ||
| 332 | #else | ||
| 333 | const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0; | ||
| 334 | #endif | ||
| 335 | |||
| 336 | if (!set_size_result) { | ||
| 337 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 338 | LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}", | ||
| 339 | PathToUTF8String(file_path), size, ec.message()); | ||
| 340 | } | ||
| 341 | |||
| 342 | return set_size_result; | ||
| 343 | } | ||
| 344 | |||
| 345 | u64 IOFile::GetSize() const { | ||
| 346 | if (!IsOpen()) { | ||
| 347 | return 0; | ||
| 348 | } | ||
| 349 | |||
| 350 | std::error_code ec; | ||
| 351 | |||
| 352 | const auto file_size = fs::file_size(file_path, ec); | ||
| 353 | |||
| 354 | if (ec) { | ||
| 355 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", | ||
| 356 | PathToUTF8String(file_path), ec.message()); | ||
| 357 | return 0; | ||
| 358 | } | ||
| 359 | |||
| 360 | return file_size; | ||
| 361 | } | ||
| 362 | |||
| 363 | bool IOFile::Seek(s64 offset, SeekOrigin origin) const { | ||
| 364 | if (!IsOpen()) { | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | |||
| 368 | errno = 0; | ||
| 369 | |||
| 370 | const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0; | ||
| 371 | |||
| 372 | if (!seek_result) { | ||
| 373 | const auto ec = std::error_code{errno, std::generic_category()}; | ||
| 374 | LOG_ERROR(Common_Filesystem, | ||
| 375 | "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", | ||
| 376 | PathToUTF8String(file_path), offset, origin, ec.message()); | ||
| 377 | } | ||
| 378 | |||
| 379 | return seek_result; | ||
| 380 | } | ||
| 381 | |||
| 382 | s64 IOFile::Tell() const { | ||
| 383 | if (!IsOpen()) { | ||
| 384 | return 0; | ||
| 385 | } | ||
| 386 | |||
| 387 | errno = 0; | ||
| 388 | |||
| 389 | return ftello(file); | ||
| 390 | } | ||
| 391 | |||
| 392 | } // namespace Common::FS | ||
diff --git a/src/common/fs/file.h b/src/common/fs/file.h new file mode 100644 index 000000000..209f9664b --- /dev/null +++ b/src/common/fs/file.h | |||
| @@ -0,0 +1,450 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstdio> | ||
| 8 | #include <filesystem> | ||
| 9 | #include <fstream> | ||
| 10 | #include <span> | ||
| 11 | #include <type_traits> | ||
| 12 | #include <vector> | ||
| 13 | |||
| 14 | #include "common/concepts.h" | ||
| 15 | #include "common/fs/fs_types.h" | ||
| 16 | #include "common/fs/fs_util.h" | ||
| 17 | |||
| 18 | namespace Common::FS { | ||
| 19 | |||
| 20 | enum class SeekOrigin { | ||
| 21 | SetOrigin, // Seeks from the start of the file. | ||
| 22 | CurrentPosition, // Seeks from the current file pointer position. | ||
| 23 | End, // Seeks from the end of the file. | ||
| 24 | }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Opens a file stream at path with the specified open mode. | ||
| 28 | * | ||
| 29 | * @param file_stream Reference to file stream | ||
| 30 | * @param path Filesystem path | ||
| 31 | * @param open_mode File stream open mode | ||
| 32 | */ | ||
| 33 | template <typename FileStream> | ||
| 34 | void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path, | ||
| 35 | std::ios_base::openmode open_mode) { | ||
| 36 | file_stream.open(path, open_mode); | ||
| 37 | } | ||
| 38 | |||
| 39 | #ifdef _WIN32 | ||
| 40 | template <typename FileStream, typename Path> | ||
| 41 | void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) { | ||
| 42 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 43 | file_stream.open(ToU8String(path), open_mode); | ||
| 44 | } else { | ||
| 45 | file_stream.open(std::filesystem::path{path}, open_mode); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | #endif | ||
| 49 | |||
| 50 | /** | ||
| 51 | * Reads an entire file at path and returns a string of the contents read from the file. | ||
| 52 | * If the filesystem object at path is not a file, this function returns an empty string. | ||
| 53 | * | ||
| 54 | * @param path Filesystem path | ||
| 55 | * @param type File type | ||
| 56 | * | ||
| 57 | * @returns A string of the contents read from the file. | ||
| 58 | */ | ||
| 59 | [[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type); | ||
| 60 | |||
| 61 | #ifdef _WIN32 | ||
| 62 | template <typename Path> | ||
| 63 | [[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) { | ||
| 64 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 65 | return ReadStringFromFile(ToU8String(path), type); | ||
| 66 | } else { | ||
| 67 | return ReadStringFromFile(std::filesystem::path{path}, type); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | #endif | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Writes a string to a file at path and returns the number of characters successfully written. | ||
| 74 | * If an file already exists at path, its contents will be erased. | ||
| 75 | * If the filesystem object at path is not a file, this function returns 0. | ||
| 76 | * | ||
| 77 | * @param path Filesystem path | ||
| 78 | * @param type File type | ||
| 79 | * | ||
| 80 | * @returns Number of characters successfully written. | ||
| 81 | */ | ||
| 82 | [[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type, | ||
| 83 | std::string_view string); | ||
| 84 | |||
| 85 | #ifdef _WIN32 | ||
| 86 | template <typename Path> | ||
| 87 | [[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) { | ||
| 88 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 89 | return WriteStringToFile(ToU8String(path), type, string); | ||
| 90 | } else { | ||
| 91 | return WriteStringToFile(std::filesystem::path{path}, type, string); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | #endif | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Appends a string to a file at path and returns the number of characters successfully written. | ||
| 98 | * If a file does not exist at path, WriteStringToFile is called instead. | ||
| 99 | * If the filesystem object at path is not a file, this function returns 0. | ||
| 100 | * | ||
| 101 | * @param path Filesystem path | ||
| 102 | * @param type File type | ||
| 103 | * | ||
| 104 | * @returns Number of characters successfully written. | ||
| 105 | */ | ||
| 106 | [[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type, | ||
| 107 | std::string_view string); | ||
| 108 | |||
| 109 | #ifdef _WIN32 | ||
| 110 | template <typename Path> | ||
| 111 | [[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) { | ||
| 112 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 113 | return AppendStringToFile(ToU8String(path), type, string); | ||
| 114 | } else { | ||
| 115 | return AppendStringToFile(std::filesystem::path{path}, type, string); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | #endif | ||
| 119 | |||
| 120 | class IOFile final : NonCopyable { | ||
| 121 | public: | ||
| 122 | IOFile(); | ||
| 123 | |||
| 124 | explicit IOFile(const std::string& path, FileAccessMode mode, | ||
| 125 | FileType type = FileType::BinaryFile, | ||
| 126 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 127 | |||
| 128 | explicit IOFile(std::string_view path, FileAccessMode mode, | ||
| 129 | FileType type = FileType::BinaryFile, | ||
| 130 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 131 | |||
| 132 | /** | ||
| 133 | * An IOFile is a lightweight wrapper on C Library file operations. | ||
| 134 | * Automatically closes an open file on the destruction of an IOFile object. | ||
| 135 | * | ||
| 136 | * @param path Filesystem path | ||
| 137 | * @param mode File access mode | ||
| 138 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 139 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 140 | */ | ||
| 141 | explicit IOFile(const std::filesystem::path& path, FileAccessMode mode, | ||
| 142 | FileType type = FileType::BinaryFile, | ||
| 143 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 144 | |||
| 145 | virtual ~IOFile(); | ||
| 146 | |||
| 147 | IOFile(IOFile&& other) noexcept; | ||
| 148 | IOFile& operator=(IOFile&& other) noexcept; | ||
| 149 | |||
| 150 | /** | ||
| 151 | * Gets the path of the file. | ||
| 152 | * | ||
| 153 | * @returns The path of the file. | ||
| 154 | */ | ||
| 155 | [[nodiscard]] std::filesystem::path GetPath() const; | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Gets the access mode of the file. | ||
| 159 | * | ||
| 160 | * @returns The access mode of the file. | ||
| 161 | */ | ||
| 162 | [[nodiscard]] FileAccessMode GetAccessMode() const; | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Gets the type of the file. | ||
| 166 | * | ||
| 167 | * @returns The type of the file. | ||
| 168 | */ | ||
| 169 | [[nodiscard]] FileType GetType() const; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Opens a file at path with the specified file access mode. | ||
| 173 | * This function behaves differently depending on the FileAccessMode. | ||
| 174 | * These behaviors are documented in each enum value of FileAccessMode. | ||
| 175 | * | ||
| 176 | * @param path Filesystem path | ||
| 177 | * @param mode File access mode | ||
| 178 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 179 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 180 | */ | ||
| 181 | void Open(const std::filesystem::path& path, FileAccessMode mode, | ||
| 182 | FileType type = FileType::BinaryFile, | ||
| 183 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 184 | |||
| 185 | #ifdef _WIN32 | ||
| 186 | template <typename Path> | ||
| 187 | [[nodiscard]] void Open(const Path& path, FileAccessMode mode, | ||
| 188 | FileType type = FileType::BinaryFile, | ||
| 189 | FileShareFlag flag = FileShareFlag::ShareReadOnly) { | ||
| 190 | using ValueType = typename Path::value_type; | ||
| 191 | if constexpr (IsChar<ValueType>) { | ||
| 192 | Open(ToU8String(path), mode, type, flag); | ||
| 193 | } else { | ||
| 194 | Open(std::filesystem::path{path}, mode, type, flag); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | #endif | ||
| 198 | |||
| 199 | /// Closes the file if it is opened. | ||
| 200 | void Close(); | ||
| 201 | |||
| 202 | /** | ||
| 203 | * Checks whether the file is open. | ||
| 204 | * Use this to check whether the calls to Open() or Close() succeeded. | ||
| 205 | * | ||
| 206 | * @returns True if the file is open, false otherwise. | ||
| 207 | */ | ||
| 208 | [[nodiscard]] bool IsOpen() const; | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Helper function which deduces the value type of a contiguous STL container used in ReadSpan. | ||
| 212 | * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls | ||
| 213 | * ReadObject and T must be a trivially copyable object. | ||
| 214 | * | ||
| 215 | * See ReadSpan for more details if T is a contiguous container. | ||
| 216 | * See ReadObject for more details if T is a trivially copyable object. | ||
| 217 | * | ||
| 218 | * @tparam T Contiguous container or trivially copyable object | ||
| 219 | * | ||
| 220 | * @param data Container of T::value_type data or reference to object | ||
| 221 | * | ||
| 222 | * @returns Count of T::value_type data or objects successfully read. | ||
| 223 | */ | ||
| 224 | template <typename T> | ||
| 225 | [[nodiscard]] size_t Read(T& data) const { | ||
| 226 | if constexpr (IsSTLContainer<T>) { | ||
| 227 | using ContiguousType = typename T::value_type; | ||
| 228 | static_assert(std::is_trivially_copyable_v<ContiguousType>, | ||
| 229 | "Data type must be trivially copyable."); | ||
| 230 | return ReadSpan<ContiguousType>(data); | ||
| 231 | } else { | ||
| 232 | return ReadObject(data) ? 1 : 0; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | /** | ||
| 237 | * Helper function which deduces the value type of a contiguous STL container used in WriteSpan. | ||
| 238 | * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls | ||
| 239 | * WriteObject and T must be a trivially copyable object. | ||
| 240 | * | ||
| 241 | * See WriteSpan for more details if T is a contiguous container. | ||
| 242 | * See WriteObject for more details if T is a trivially copyable object. | ||
| 243 | * | ||
| 244 | * @tparam T Contiguous container or trivially copyable object | ||
| 245 | * | ||
| 246 | * @param data Container of T::value_type data or const reference to object | ||
| 247 | * | ||
| 248 | * @returns Count of T::value_type data or objects successfully written. | ||
| 249 | */ | ||
| 250 | template <typename T> | ||
| 251 | [[nodiscard]] size_t Write(const T& data) const { | ||
| 252 | if constexpr (IsSTLContainer<T>) { | ||
| 253 | using ContiguousType = typename T::value_type; | ||
| 254 | static_assert(std::is_trivially_copyable_v<ContiguousType>, | ||
| 255 | "Data type must be trivially copyable."); | ||
| 256 | return WriteSpan<ContiguousType>(data); | ||
| 257 | } else { | ||
| 258 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 259 | return WriteObject(data) ? 1 : 0; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Reads a span of T data from a file sequentially. | ||
| 265 | * This function reads from the current position of the file pointer and | ||
| 266 | * advances it by the (count of T * sizeof(T)) bytes successfully read. | ||
| 267 | * | ||
| 268 | * Failures occur when: | ||
| 269 | * - The file is not open | ||
| 270 | * - The opened file lacks read permissions | ||
| 271 | * - Attempting to read beyond the end-of-file | ||
| 272 | * | ||
| 273 | * @tparam T Data type | ||
| 274 | * | ||
| 275 | * @param data Span of T data | ||
| 276 | * | ||
| 277 | * @returns Count of T data successfully read. | ||
| 278 | */ | ||
| 279 | template <typename T> | ||
| 280 | [[nodiscard]] size_t ReadSpan(std::span<T> data) const { | ||
| 281 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 282 | |||
| 283 | if (!IsOpen()) { | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | return std::fread(data.data(), sizeof(T), data.size(), file); | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * Writes a span of T data to a file sequentially. | ||
| 292 | * This function writes from the current position of the file pointer and | ||
| 293 | * advances it by the (count of T * sizeof(T)) bytes successfully written. | ||
| 294 | * | ||
| 295 | * Failures occur when: | ||
| 296 | * - The file is not open | ||
| 297 | * - The opened file lacks write permissions | ||
| 298 | * | ||
| 299 | * @tparam T Data type | ||
| 300 | * | ||
| 301 | * @param data Span of T data | ||
| 302 | * | ||
| 303 | * @returns Count of T data successfully written. | ||
| 304 | */ | ||
| 305 | template <typename T> | ||
| 306 | [[nodiscard]] size_t WriteSpan(std::span<const T> data) const { | ||
| 307 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 308 | |||
| 309 | if (!IsOpen()) { | ||
| 310 | return 0; | ||
| 311 | } | ||
| 312 | |||
| 313 | return std::fwrite(data.data(), sizeof(T), data.size(), file); | ||
| 314 | } | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Reads a T object from a file sequentially. | ||
| 318 | * This function reads from the current position of the file pointer and | ||
| 319 | * advances it by the sizeof(T) bytes successfully read. | ||
| 320 | * | ||
| 321 | * Failures occur when: | ||
| 322 | * - The file is not open | ||
| 323 | * - The opened file lacks read permissions | ||
| 324 | * - Attempting to read beyond the end-of-file | ||
| 325 | * | ||
| 326 | * @tparam T Data type | ||
| 327 | * | ||
| 328 | * @param object Reference to object | ||
| 329 | * | ||
| 330 | * @returns True if the object is successfully read from the file, false otherwise. | ||
| 331 | */ | ||
| 332 | template <typename T> | ||
| 333 | [[nodiscard]] bool ReadObject(T& object) const { | ||
| 334 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 335 | static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); | ||
| 336 | |||
| 337 | if (!IsOpen()) { | ||
| 338 | return false; | ||
| 339 | } | ||
| 340 | |||
| 341 | return std::fread(&object, sizeof(T), 1, file) == 1; | ||
| 342 | } | ||
| 343 | |||
| 344 | /** | ||
| 345 | * Writes a T object to a file sequentially. | ||
| 346 | * This function writes from the current position of the file pointer and | ||
| 347 | * advances it by the sizeof(T) bytes successfully written. | ||
| 348 | * | ||
| 349 | * Failures occur when: | ||
| 350 | * - The file is not open | ||
| 351 | * - The opened file lacks write permissions | ||
| 352 | * | ||
| 353 | * @tparam T Data type | ||
| 354 | * | ||
| 355 | * @param object Const reference to object | ||
| 356 | * | ||
| 357 | * @returns True if the object is successfully written to the file, false otherwise. | ||
| 358 | */ | ||
| 359 | template <typename T> | ||
| 360 | [[nodiscard]] bool WriteObject(const T& object) const { | ||
| 361 | static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); | ||
| 362 | static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); | ||
| 363 | |||
| 364 | if (!IsOpen()) { | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | |||
| 368 | return std::fwrite(&object, sizeof(T), 1, file) == 1; | ||
| 369 | } | ||
| 370 | |||
| 371 | /** | ||
| 372 | * Specialized function to read a string of a given length from a file sequentially. | ||
| 373 | * This function writes from the current position of the file pointer and | ||
| 374 | * advances it by the number of characters successfully read. | ||
| 375 | * The size of the returned string may not match length if not all bytes are successfully read. | ||
| 376 | * | ||
| 377 | * @param length Length of the string | ||
| 378 | * | ||
| 379 | * @returns A string read from the file. | ||
| 380 | */ | ||
| 381 | [[nodiscard]] std::string ReadString(size_t length) const; | ||
| 382 | |||
| 383 | /** | ||
| 384 | * Specialized function to write a string to a file sequentially. | ||
| 385 | * This function writes from the current position of the file pointer and | ||
| 386 | * advances it by the number of characters successfully written. | ||
| 387 | * | ||
| 388 | * @param string Span of const char backed std::string or std::string_view | ||
| 389 | * | ||
| 390 | * @returns Number of characters successfully written. | ||
| 391 | */ | ||
| 392 | [[nodiscard]] size_t WriteString(std::span<const char> string) const; | ||
| 393 | |||
| 394 | /** | ||
| 395 | * Flushes any unwritten buffered data into the file. | ||
| 396 | * | ||
| 397 | * @returns True if the flush was successful, false otherwise. | ||
| 398 | */ | ||
| 399 | [[nodiscard]] bool Flush() const; | ||
| 400 | |||
| 401 | /** | ||
| 402 | * Resizes the file to a given size. | ||
| 403 | * If the file is resized to a smaller size, the remainder of the file is discarded. | ||
| 404 | * If the file is resized to a larger size, the new area appears as if zero-filled. | ||
| 405 | * | ||
| 406 | * Failures occur when: | ||
| 407 | * - The file is not open | ||
| 408 | * | ||
| 409 | * @param size File size in bytes | ||
| 410 | * | ||
| 411 | * @returns True if the file resize succeeded, false otherwise. | ||
| 412 | */ | ||
| 413 | [[nodiscard]] bool SetSize(u64 size) const; | ||
| 414 | |||
| 415 | /** | ||
| 416 | * Gets the size of the file. | ||
| 417 | * | ||
| 418 | * Failures occur when: | ||
| 419 | * - The file is not open | ||
| 420 | * | ||
| 421 | * @returns The file size in bytes of the file. Returns 0 on failure. | ||
| 422 | */ | ||
| 423 | [[nodiscard]] u64 GetSize() const; | ||
| 424 | |||
| 425 | /** | ||
| 426 | * Moves the current position of the file pointer with the specified offset and seek origin. | ||
| 427 | * | ||
| 428 | * @param offset Offset from seek origin | ||
| 429 | * @param origin Seek origin | ||
| 430 | * | ||
| 431 | * @returns True if the file pointer has moved to the specified offset, false otherwise. | ||
| 432 | */ | ||
| 433 | [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; | ||
| 434 | |||
| 435 | /** | ||
| 436 | * Gets the current position of the file pointer. | ||
| 437 | * | ||
| 438 | * @returns The current position of the file pointer. | ||
| 439 | */ | ||
| 440 | [[nodiscard]] s64 Tell() const; | ||
| 441 | |||
| 442 | private: | ||
| 443 | std::filesystem::path file_path; | ||
| 444 | FileAccessMode file_access_mode; | ||
| 445 | FileType file_type; | ||
| 446 | |||
| 447 | std::FILE* file = nullptr; | ||
| 448 | }; | ||
| 449 | |||
| 450 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp new file mode 100644 index 000000000..d492480d9 --- /dev/null +++ b/src/common/fs/fs.cpp | |||
| @@ -0,0 +1,610 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | |||
| 10 | namespace Common::FS { | ||
| 11 | |||
| 12 | namespace fs = std::filesystem; | ||
| 13 | |||
| 14 | // File Operations | ||
| 15 | |||
| 16 | bool NewFile(const fs::path& path, u64 size) { | ||
| 17 | if (!ValidatePath(path)) { | ||
| 18 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 19 | return false; | ||
| 20 | } | ||
| 21 | |||
| 22 | if (!Exists(path.parent_path())) { | ||
| 23 | LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", | ||
| 24 | PathToUTF8String(path)); | ||
| 25 | return false; | ||
| 26 | } | ||
| 27 | |||
| 28 | if (Exists(path)) { | ||
| 29 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path)); | ||
| 30 | return false; | ||
| 31 | } | ||
| 32 | |||
| 33 | IOFile io_file{path, FileAccessMode::Write}; | ||
| 34 | |||
| 35 | if (!io_file.IsOpen()) { | ||
| 36 | LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path)); | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | |||
| 40 | if (!io_file.SetSize(size)) { | ||
| 41 | LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}", | ||
| 42 | PathToUTF8String(path), size); | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | |||
| 46 | io_file.Close(); | ||
| 47 | |||
| 48 | LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}", | ||
| 49 | PathToUTF8String(path), size); | ||
| 50 | |||
| 51 | return true; | ||
| 52 | } | ||
| 53 | |||
| 54 | bool RemoveFile(const fs::path& path) { | ||
| 55 | if (!ValidatePath(path)) { | ||
| 56 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | |||
| 60 | if (!Exists(path)) { | ||
| 61 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 62 | PathToUTF8String(path)); | ||
| 63 | return true; | ||
| 64 | } | ||
| 65 | |||
| 66 | if (!IsFile(path)) { | ||
| 67 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", | ||
| 68 | PathToUTF8String(path)); | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | std::error_code ec; | ||
| 73 | |||
| 74 | fs::remove(path, ec); | ||
| 75 | |||
| 76 | if (ec) { | ||
| 77 | LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}", | ||
| 78 | PathToUTF8String(path), ec.message()); | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}", | ||
| 83 | PathToUTF8String(path)); | ||
| 84 | |||
| 85 | return true; | ||
| 86 | } | ||
| 87 | |||
| 88 | bool RenameFile(const fs::path& old_path, const fs::path& new_path) { | ||
| 89 | if (!ValidatePath(old_path) || !ValidatePath(new_path)) { | ||
| 90 | LOG_ERROR(Common_Filesystem, | ||
| 91 | "One or both input path(s) is not valid, old_path={}, new_path={}", | ||
| 92 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (!Exists(old_path)) { | ||
| 97 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", | ||
| 98 | PathToUTF8String(old_path)); | ||
| 99 | return false; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!IsFile(old_path)) { | ||
| 103 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file", | ||
| 104 | PathToUTF8String(old_path)); | ||
| 105 | return false; | ||
| 106 | } | ||
| 107 | |||
| 108 | if (Exists(new_path)) { | ||
| 109 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", | ||
| 110 | PathToUTF8String(new_path)); | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | |||
| 114 | std::error_code ec; | ||
| 115 | |||
| 116 | fs::rename(old_path, new_path, ec); | ||
| 117 | |||
| 118 | if (ec) { | ||
| 119 | LOG_ERROR(Common_Filesystem, | ||
| 120 | "Failed to rename the file from old_path={} to new_path={}, ec_message={}", | ||
| 121 | PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | |||
| 125 | LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", | ||
| 126 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 127 | |||
| 128 | return true; | ||
| 129 | } | ||
| 130 | |||
| 131 | std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, FileType type, | ||
| 132 | FileShareFlag flag) { | ||
| 133 | if (!ValidatePath(path)) { | ||
| 134 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 135 | return nullptr; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (!IsFile(path)) { | ||
| 139 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", | ||
| 140 | PathToUTF8String(path)); | ||
| 141 | return nullptr; | ||
| 142 | } | ||
| 143 | |||
| 144 | auto io_file = std::make_shared<IOFile>(path, mode, type, flag); | ||
| 145 | |||
| 146 | if (!io_file->IsOpen()) { | ||
| 147 | io_file.reset(); | ||
| 148 | |||
| 149 | LOG_ERROR(Common_Filesystem, | ||
| 150 | "Failed to open the file at path={} with mode={}, type={}, flag={}", | ||
| 151 | PathToUTF8String(path), mode, type, flag); | ||
| 152 | |||
| 153 | return nullptr; | ||
| 154 | } | ||
| 155 | |||
| 156 | LOG_DEBUG(Common_Filesystem, | ||
| 157 | "Successfully opened the file at path={} with mode={}, type={}, flag={}", | ||
| 158 | PathToUTF8String(path), mode, type, flag); | ||
| 159 | |||
| 160 | return io_file; | ||
| 161 | } | ||
| 162 | |||
| 163 | // Directory Operations | ||
| 164 | |||
| 165 | bool CreateDir(const fs::path& path) { | ||
| 166 | if (!ValidatePath(path)) { | ||
| 167 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 168 | return false; | ||
| 169 | } | ||
| 170 | |||
| 171 | if (!Exists(path.parent_path())) { | ||
| 172 | LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", | ||
| 173 | PathToUTF8String(path)); | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | if (IsDir(path)) { | ||
| 178 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", | ||
| 179 | PathToUTF8String(path)); | ||
| 180 | return true; | ||
| 181 | } | ||
| 182 | |||
| 183 | std::error_code ec; | ||
| 184 | |||
| 185 | fs::create_directory(path, ec); | ||
| 186 | |||
| 187 | if (ec) { | ||
| 188 | LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}", | ||
| 189 | PathToUTF8String(path), ec.message()); | ||
| 190 | return false; | ||
| 191 | } | ||
| 192 | |||
| 193 | LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}", | ||
| 194 | PathToUTF8String(path)); | ||
| 195 | |||
| 196 | return true; | ||
| 197 | } | ||
| 198 | |||
| 199 | bool CreateDirs(const fs::path& path) { | ||
| 200 | if (!ValidatePath(path)) { | ||
| 201 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | if (IsDir(path)) { | ||
| 206 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", | ||
| 207 | PathToUTF8String(path)); | ||
| 208 | return true; | ||
| 209 | } | ||
| 210 | |||
| 211 | std::error_code ec; | ||
| 212 | |||
| 213 | fs::create_directories(path, ec); | ||
| 214 | |||
| 215 | if (ec) { | ||
| 216 | LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}", | ||
| 217 | PathToUTF8String(path), ec.message()); | ||
| 218 | return false; | ||
| 219 | } | ||
| 220 | |||
| 221 | LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}", | ||
| 222 | PathToUTF8String(path)); | ||
| 223 | |||
| 224 | return true; | ||
| 225 | } | ||
| 226 | |||
| 227 | bool CreateParentDir(const fs::path& path) { | ||
| 228 | return CreateDir(path.parent_path()); | ||
| 229 | } | ||
| 230 | |||
| 231 | bool CreateParentDirs(const fs::path& path) { | ||
| 232 | return CreateDirs(path.parent_path()); | ||
| 233 | } | ||
| 234 | |||
| 235 | bool RemoveDir(const fs::path& path) { | ||
| 236 | if (!ValidatePath(path)) { | ||
| 237 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 238 | return false; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (!Exists(path)) { | ||
| 242 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 243 | PathToUTF8String(path)); | ||
| 244 | return true; | ||
| 245 | } | ||
| 246 | |||
| 247 | if (!IsDir(path)) { | ||
| 248 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 249 | PathToUTF8String(path)); | ||
| 250 | return false; | ||
| 251 | } | ||
| 252 | |||
| 253 | std::error_code ec; | ||
| 254 | |||
| 255 | fs::remove(path, ec); | ||
| 256 | |||
| 257 | if (ec) { | ||
| 258 | LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}", | ||
| 259 | PathToUTF8String(path), ec.message()); | ||
| 260 | return false; | ||
| 261 | } | ||
| 262 | |||
| 263 | LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}", | ||
| 264 | PathToUTF8String(path)); | ||
| 265 | |||
| 266 | return true; | ||
| 267 | } | ||
| 268 | |||
| 269 | bool RemoveDirRecursively(const fs::path& path) { | ||
| 270 | if (!ValidatePath(path)) { | ||
| 271 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 272 | return false; | ||
| 273 | } | ||
| 274 | |||
| 275 | if (!Exists(path)) { | ||
| 276 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 277 | PathToUTF8String(path)); | ||
| 278 | return true; | ||
| 279 | } | ||
| 280 | |||
| 281 | if (!IsDir(path)) { | ||
| 282 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 283 | PathToUTF8String(path)); | ||
| 284 | return false; | ||
| 285 | } | ||
| 286 | |||
| 287 | std::error_code ec; | ||
| 288 | |||
| 289 | fs::remove_all(path, ec); | ||
| 290 | |||
| 291 | if (ec) { | ||
| 292 | LOG_ERROR(Common_Filesystem, | ||
| 293 | "Failed to remove the directory and its contents at path={}, ec_message={}", | ||
| 294 | PathToUTF8String(path), ec.message()); | ||
| 295 | return false; | ||
| 296 | } | ||
| 297 | |||
| 298 | LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}", | ||
| 299 | PathToUTF8String(path)); | ||
| 300 | |||
| 301 | return true; | ||
| 302 | } | ||
| 303 | |||
| 304 | bool RemoveDirContentsRecursively(const fs::path& path) { | ||
| 305 | if (!ValidatePath(path)) { | ||
| 306 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 307 | return false; | ||
| 308 | } | ||
| 309 | |||
| 310 | if (!Exists(path)) { | ||
| 311 | LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 312 | PathToUTF8String(path)); | ||
| 313 | return true; | ||
| 314 | } | ||
| 315 | |||
| 316 | if (!IsDir(path)) { | ||
| 317 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 318 | PathToUTF8String(path)); | ||
| 319 | return false; | ||
| 320 | } | ||
| 321 | |||
| 322 | std::error_code ec; | ||
| 323 | |||
| 324 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | ||
| 325 | if (ec) { | ||
| 326 | LOG_ERROR(Common_Filesystem, | ||
| 327 | "Failed to completely enumerate the directory at path={}, ec_message={}", | ||
| 328 | PathToUTF8String(path), ec.message()); | ||
| 329 | break; | ||
| 330 | } | ||
| 331 | |||
| 332 | fs::remove(entry.path(), ec); | ||
| 333 | |||
| 334 | if (ec) { | ||
| 335 | LOG_ERROR(Common_Filesystem, | ||
| 336 | "Failed to remove the filesystem object at path={}, ec_message={}", | ||
| 337 | PathToUTF8String(entry.path()), ec.message()); | ||
| 338 | break; | ||
| 339 | } | ||
| 340 | } | ||
| 341 | |||
| 342 | if (ec) { | ||
| 343 | LOG_ERROR(Common_Filesystem, | ||
| 344 | "Failed to remove all the contents of the directory at path={}, ec_message={}", | ||
| 345 | PathToUTF8String(path), ec.message()); | ||
| 346 | return false; | ||
| 347 | } | ||
| 348 | |||
| 349 | LOG_DEBUG(Common_Filesystem, | ||
| 350 | "Successfully removed all the contents of the directory at path={}", | ||
| 351 | PathToUTF8String(path)); | ||
| 352 | |||
| 353 | return true; | ||
| 354 | } | ||
| 355 | |||
| 356 | bool RenameDir(const fs::path& old_path, const fs::path& new_path) { | ||
| 357 | if (!ValidatePath(old_path) || !ValidatePath(new_path)) { | ||
| 358 | LOG_ERROR(Common_Filesystem, | ||
| 359 | "One or both input path(s) is not valid, old_path={}, new_path={}", | ||
| 360 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 361 | return false; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (!Exists(old_path)) { | ||
| 365 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", | ||
| 366 | PathToUTF8String(old_path)); | ||
| 367 | return false; | ||
| 368 | } | ||
| 369 | |||
| 370 | if (!IsDir(old_path)) { | ||
| 371 | LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory", | ||
| 372 | PathToUTF8String(old_path)); | ||
| 373 | return false; | ||
| 374 | } | ||
| 375 | |||
| 376 | if (Exists(new_path)) { | ||
| 377 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", | ||
| 378 | PathToUTF8String(new_path)); | ||
| 379 | return false; | ||
| 380 | } | ||
| 381 | |||
| 382 | std::error_code ec; | ||
| 383 | |||
| 384 | fs::rename(old_path, new_path, ec); | ||
| 385 | |||
| 386 | if (ec) { | ||
| 387 | LOG_ERROR(Common_Filesystem, | ||
| 388 | "Failed to rename the file from old_path={} to new_path={}, ec_message={}", | ||
| 389 | PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); | ||
| 390 | return false; | ||
| 391 | } | ||
| 392 | |||
| 393 | LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", | ||
| 394 | PathToUTF8String(old_path), PathToUTF8String(new_path)); | ||
| 395 | |||
| 396 | return true; | ||
| 397 | } | ||
| 398 | |||
| 399 | void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, | ||
| 400 | DirEntryFilter filter) { | ||
| 401 | if (!ValidatePath(path)) { | ||
| 402 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | |||
| 406 | if (!Exists(path)) { | ||
| 407 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 408 | PathToUTF8String(path)); | ||
| 409 | return; | ||
| 410 | } | ||
| 411 | |||
| 412 | if (!IsDir(path)) { | ||
| 413 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 414 | PathToUTF8String(path)); | ||
| 415 | return; | ||
| 416 | } | ||
| 417 | |||
| 418 | bool callback_error = false; | ||
| 419 | |||
| 420 | std::error_code ec; | ||
| 421 | |||
| 422 | for (const auto& entry : fs::directory_iterator(path, ec)) { | ||
| 423 | if (ec) { | ||
| 424 | break; | ||
| 425 | } | ||
| 426 | |||
| 427 | if (True(filter & DirEntryFilter::File) && | ||
| 428 | entry.status().type() == fs::file_type::regular) { | ||
| 429 | if (!callback(entry.path())) { | ||
| 430 | callback_error = true; | ||
| 431 | break; | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | if (True(filter & DirEntryFilter::Directory) && | ||
| 436 | entry.status().type() == fs::file_type::directory) { | ||
| 437 | if (!callback(entry.path())) { | ||
| 438 | callback_error = true; | ||
| 439 | break; | ||
| 440 | } | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | if (callback_error || ec) { | ||
| 445 | LOG_ERROR(Common_Filesystem, | ||
| 446 | "Failed to visit all the directory entries of path={}, ec_message={}", | ||
| 447 | PathToUTF8String(path), ec.message()); | ||
| 448 | return; | ||
| 449 | } | ||
| 450 | |||
| 451 | LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", | ||
| 452 | PathToUTF8String(path)); | ||
| 453 | } | ||
| 454 | |||
| 455 | void IterateDirEntriesRecursively(const std::filesystem::path& path, | ||
| 456 | const DirEntryCallable& callback, DirEntryFilter filter) { | ||
| 457 | if (!ValidatePath(path)) { | ||
| 458 | LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); | ||
| 459 | return; | ||
| 460 | } | ||
| 461 | |||
| 462 | if (!Exists(path)) { | ||
| 463 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", | ||
| 464 | PathToUTF8String(path)); | ||
| 465 | return; | ||
| 466 | } | ||
| 467 | |||
| 468 | if (!IsDir(path)) { | ||
| 469 | LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", | ||
| 470 | PathToUTF8String(path)); | ||
| 471 | return; | ||
| 472 | } | ||
| 473 | |||
| 474 | bool callback_error = false; | ||
| 475 | |||
| 476 | std::error_code ec; | ||
| 477 | |||
| 478 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | ||
| 479 | if (ec) { | ||
| 480 | break; | ||
| 481 | } | ||
| 482 | |||
| 483 | if (True(filter & DirEntryFilter::File) && | ||
| 484 | entry.status().type() == fs::file_type::regular) { | ||
| 485 | if (!callback(entry.path())) { | ||
| 486 | callback_error = true; | ||
| 487 | break; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | if (True(filter & DirEntryFilter::Directory) && | ||
| 492 | entry.status().type() == fs::file_type::directory) { | ||
| 493 | if (!callback(entry.path())) { | ||
| 494 | callback_error = true; | ||
| 495 | break; | ||
| 496 | } | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | if (callback_error || ec) { | ||
| 501 | LOG_ERROR(Common_Filesystem, | ||
| 502 | "Failed to visit all the directory entries of path={}, ec_message={}", | ||
| 503 | PathToUTF8String(path), ec.message()); | ||
| 504 | return; | ||
| 505 | } | ||
| 506 | |||
| 507 | LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", | ||
| 508 | PathToUTF8String(path)); | ||
| 509 | } | ||
| 510 | |||
| 511 | // Generic Filesystem Operations | ||
| 512 | |||
| 513 | bool Exists(const fs::path& path) { | ||
| 514 | return fs::exists(path); | ||
| 515 | } | ||
| 516 | |||
| 517 | bool IsFile(const fs::path& path) { | ||
| 518 | return fs::is_regular_file(path); | ||
| 519 | } | ||
| 520 | |||
| 521 | bool IsDir(const fs::path& path) { | ||
| 522 | return fs::is_directory(path); | ||
| 523 | } | ||
| 524 | |||
| 525 | fs::path GetCurrentDir() { | ||
| 526 | std::error_code ec; | ||
| 527 | |||
| 528 | const auto current_path = fs::current_path(ec); | ||
| 529 | |||
| 530 | if (ec) { | ||
| 531 | LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message()); | ||
| 532 | return {}; | ||
| 533 | } | ||
| 534 | |||
| 535 | return current_path; | ||
| 536 | } | ||
| 537 | |||
| 538 | bool SetCurrentDir(const fs::path& path) { | ||
| 539 | std::error_code ec; | ||
| 540 | |||
| 541 | fs::current_path(path, ec); | ||
| 542 | |||
| 543 | if (ec) { | ||
| 544 | LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}", | ||
| 545 | PathToUTF8String(path), ec.message()); | ||
| 546 | return false; | ||
| 547 | } | ||
| 548 | |||
| 549 | return true; | ||
| 550 | } | ||
| 551 | |||
| 552 | fs::file_type GetEntryType(const fs::path& path) { | ||
| 553 | std::error_code ec; | ||
| 554 | |||
| 555 | const auto file_status = fs::status(path, ec); | ||
| 556 | |||
| 557 | if (ec) { | ||
| 558 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}", | ||
| 559 | PathToUTF8String(path), ec.message()); | ||
| 560 | return fs::file_type::not_found; | ||
| 561 | } | ||
| 562 | |||
| 563 | return file_status.type(); | ||
| 564 | } | ||
| 565 | |||
| 566 | u64 GetSize(const fs::path& path) { | ||
| 567 | std::error_code ec; | ||
| 568 | |||
| 569 | const auto file_size = fs::file_size(path, ec); | ||
| 570 | |||
| 571 | if (ec) { | ||
| 572 | LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", | ||
| 573 | PathToUTF8String(path), ec.message()); | ||
| 574 | return 0; | ||
| 575 | } | ||
| 576 | |||
| 577 | return file_size; | ||
| 578 | } | ||
| 579 | |||
| 580 | u64 GetFreeSpaceSize(const fs::path& path) { | ||
| 581 | std::error_code ec; | ||
| 582 | |||
| 583 | const auto space_info = fs::space(path, ec); | ||
| 584 | |||
| 585 | if (ec) { | ||
| 586 | LOG_ERROR(Common_Filesystem, | ||
| 587 | "Failed to retrieve the available free space of path={}, ec_message={}", | ||
| 588 | PathToUTF8String(path), ec.message()); | ||
| 589 | return 0; | ||
| 590 | } | ||
| 591 | |||
| 592 | return space_info.free; | ||
| 593 | } | ||
| 594 | |||
| 595 | u64 GetTotalSpaceSize(const fs::path& path) { | ||
| 596 | std::error_code ec; | ||
| 597 | |||
| 598 | const auto space_info = fs::space(path, ec); | ||
| 599 | |||
| 600 | if (ec) { | ||
| 601 | LOG_ERROR(Common_Filesystem, | ||
| 602 | "Failed to retrieve the total capacity of path={}, ec_message={}", | ||
| 603 | PathToUTF8String(path), ec.message()); | ||
| 604 | return 0; | ||
| 605 | } | ||
| 606 | |||
| 607 | return space_info.capacity; | ||
| 608 | } | ||
| 609 | |||
| 610 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs.h b/src/common/fs/fs.h new file mode 100644 index 000000000..f6f256349 --- /dev/null +++ b/src/common/fs/fs.h | |||
| @@ -0,0 +1,582 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <filesystem> | ||
| 8 | #include <memory> | ||
| 9 | |||
| 10 | #include "common/fs/fs_types.h" | ||
| 11 | #include "common/fs/fs_util.h" | ||
| 12 | |||
| 13 | namespace Common::FS { | ||
| 14 | |||
| 15 | class IOFile; | ||
| 16 | |||
| 17 | // File Operations | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Creates a new file at path with the specified size. | ||
| 21 | * | ||
| 22 | * Failures occur when: | ||
| 23 | * - Input path is not valid | ||
| 24 | * - The input path's parent directory does not exist | ||
| 25 | * - Filesystem object at path exists | ||
| 26 | * - Filesystem at path is read only | ||
| 27 | * | ||
| 28 | * @param path Filesystem path | ||
| 29 | * @param size File size | ||
| 30 | * | ||
| 31 | * @returns True if the file creation succeeds, false otherwise. | ||
| 32 | */ | ||
| 33 | [[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0); | ||
| 34 | |||
| 35 | #ifdef _WIN32 | ||
| 36 | template <typename Path> | ||
| 37 | [[nodiscard]] bool NewFile(const Path& path, u64 size = 0) { | ||
| 38 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 39 | return NewFile(ToU8String(path), size); | ||
| 40 | } else { | ||
| 41 | return NewFile(std::filesystem::path{path}, size); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | #endif | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Removes a file at path. | ||
| 48 | * | ||
| 49 | * Failures occur when: | ||
| 50 | * - Input path is not valid | ||
| 51 | * - Filesystem object at path is not a file | ||
| 52 | * - Filesystem at path is read only | ||
| 53 | * | ||
| 54 | * @param path Filesystem path | ||
| 55 | * | ||
| 56 | * @returns True if file removal succeeds or file does not exist, false otherwise. | ||
| 57 | */ | ||
| 58 | [[nodiscard]] bool RemoveFile(const std::filesystem::path& path); | ||
| 59 | |||
| 60 | #ifdef _WIN32 | ||
| 61 | template <typename Path> | ||
| 62 | [[nodiscard]] bool RemoveFile(const Path& path) { | ||
| 63 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 64 | return RemoveFile(ToU8String(path)); | ||
| 65 | } else { | ||
| 66 | return RemoveFile(std::filesystem::path{path}); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | #endif | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Renames a file from old_path to new_path. | ||
| 73 | * | ||
| 74 | * Failures occur when: | ||
| 75 | * - One or both input path(s) is not valid | ||
| 76 | * - Filesystem object at old_path does not exist | ||
| 77 | * - Filesystem object at old_path is not a file | ||
| 78 | * - Filesystem object at new_path exists | ||
| 79 | * - Filesystem at either path is read only | ||
| 80 | * | ||
| 81 | * @param old_path Old filesystem path | ||
| 82 | * @param new_path New filesystem path | ||
| 83 | * | ||
| 84 | * @returns True if file rename succeeds, false otherwise. | ||
| 85 | */ | ||
| 86 | [[nodiscard]] bool RenameFile(const std::filesystem::path& old_path, | ||
| 87 | const std::filesystem::path& new_path); | ||
| 88 | |||
| 89 | #ifdef _WIN32 | ||
| 90 | template <typename Path1, typename Path2> | ||
| 91 | [[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) { | ||
| 92 | using ValueType1 = typename Path1::value_type; | ||
| 93 | using ValueType2 = typename Path2::value_type; | ||
| 94 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 95 | return RenameFile(ToU8String(old_path), ToU8String(new_path)); | ||
| 96 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 97 | return RenameFile(ToU8String(old_path), new_path); | ||
| 98 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 99 | return RenameFile(old_path, ToU8String(new_path)); | ||
| 100 | } else { | ||
| 101 | return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path}); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | #endif | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Opens a file at path with the specified file access mode. | ||
| 108 | * This function behaves differently depending on the FileAccessMode. | ||
| 109 | * These behaviors are documented in each enum value of FileAccessMode. | ||
| 110 | * | ||
| 111 | * Failures occur when: | ||
| 112 | * - Input path is not valid | ||
| 113 | * - Filesystem object at path is not a file | ||
| 114 | * - The file is not opened | ||
| 115 | * | ||
| 116 | * @param path Filesystem path | ||
| 117 | * @param mode File access mode | ||
| 118 | * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file | ||
| 119 | * @param flag (Windows only) File-share access flag, default is ShareReadOnly | ||
| 120 | * | ||
| 121 | * @returns A shared pointer to the opened file. Returns nullptr on failure. | ||
| 122 | */ | ||
| 123 | [[nodiscard]] std::shared_ptr<IOFile> FileOpen(const std::filesystem::path& path, | ||
| 124 | FileAccessMode mode, | ||
| 125 | FileType type = FileType::BinaryFile, | ||
| 126 | FileShareFlag flag = FileShareFlag::ShareReadOnly); | ||
| 127 | |||
| 128 | #ifdef _WIN32 | ||
| 129 | template <typename Path> | ||
| 130 | [[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode, | ||
| 131 | FileType type = FileType::BinaryFile, | ||
| 132 | FileShareFlag flag = FileShareFlag::ShareReadOnly) { | ||
| 133 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 134 | return FileOpen(ToU8String(path), mode, type, flag); | ||
| 135 | } else { | ||
| 136 | return FileOpen(std::filesystem::path{path}, mode, type, flag); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | #endif | ||
| 140 | |||
| 141 | // Directory Operations | ||
| 142 | |||
| 143 | /** | ||
| 144 | * Creates a directory at path. | ||
| 145 | * Note that this function will *always* assume that the input path is a directory. For example, | ||
| 146 | * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". | ||
| 147 | * If you intend to create the parent directory of a file, use CreateParentDir instead. | ||
| 148 | * | ||
| 149 | * Failures occur when: | ||
| 150 | * - Input path is not valid | ||
| 151 | * - The input path's parent directory does not exist | ||
| 152 | * - Filesystem at path is read only | ||
| 153 | * | ||
| 154 | * @param path Filesystem path | ||
| 155 | * | ||
| 156 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 157 | */ | ||
| 158 | [[nodiscard]] bool CreateDir(const std::filesystem::path& path); | ||
| 159 | |||
| 160 | #ifdef _WIN32 | ||
| 161 | template <typename Path> | ||
| 162 | [[nodiscard]] bool CreateDir(const Path& path) { | ||
| 163 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 164 | return CreateDir(ToU8String(path)); | ||
| 165 | } else { | ||
| 166 | return CreateDir(std::filesystem::path{path}); | ||
| 167 | } | ||
| 168 | } | ||
| 169 | #endif | ||
| 170 | |||
| 171 | /** | ||
| 172 | * Recursively creates a directory at path. | ||
| 173 | * Note that this function will *always* assume that the input path is a directory. For example, | ||
| 174 | * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". | ||
| 175 | * If you intend to create the parent directory of a file, use CreateParentDirs instead. | ||
| 176 | * Unlike CreateDir, this creates all of input path's parent directories if they do not exist. | ||
| 177 | * | ||
| 178 | * Failures occur when: | ||
| 179 | * - Input path is not valid | ||
| 180 | * - Filesystem at path is read only | ||
| 181 | * | ||
| 182 | * @param path Filesystem path | ||
| 183 | * | ||
| 184 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 185 | */ | ||
| 186 | [[nodiscard]] bool CreateDirs(const std::filesystem::path& path); | ||
| 187 | |||
| 188 | #ifdef _WIN32 | ||
| 189 | template <typename Path> | ||
| 190 | [[nodiscard]] bool CreateDirs(const Path& path) { | ||
| 191 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 192 | return CreateDirs(ToU8String(path)); | ||
| 193 | } else { | ||
| 194 | return CreateDirs(std::filesystem::path{path}); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | #endif | ||
| 198 | |||
| 199 | /** | ||
| 200 | * Creates the parent directory of a given path. | ||
| 201 | * This function calls CreateDir(path.parent_path()), see CreateDir for more details. | ||
| 202 | * | ||
| 203 | * @param path Filesystem path | ||
| 204 | * | ||
| 205 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 206 | */ | ||
| 207 | [[nodiscard]] bool CreateParentDir(const std::filesystem::path& path); | ||
| 208 | |||
| 209 | #ifdef _WIN32 | ||
| 210 | template <typename Path> | ||
| 211 | [[nodiscard]] bool CreateParentDir(const Path& path) { | ||
| 212 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 213 | return CreateParentDir(ToU8String(path)); | ||
| 214 | } else { | ||
| 215 | return CreateParentDir(std::filesystem::path{path}); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | #endif | ||
| 219 | |||
| 220 | /** | ||
| 221 | * Recursively creates the parent directory of a given path. | ||
| 222 | * This function calls CreateDirs(path.parent_path()), see CreateDirs for more details. | ||
| 223 | * | ||
| 224 | * @param path Filesystem path | ||
| 225 | * | ||
| 226 | * @returns True if directory creation succeeds or directory already exists, false otherwise. | ||
| 227 | */ | ||
| 228 | [[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path); | ||
| 229 | |||
| 230 | #ifdef _WIN32 | ||
| 231 | template <typename Path> | ||
| 232 | [[nodiscard]] bool CreateParentDirs(const Path& path) { | ||
| 233 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 234 | return CreateParentDirs(ToU8String(path)); | ||
| 235 | } else { | ||
| 236 | return CreateParentDirs(std::filesystem::path{path}); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | #endif | ||
| 240 | |||
| 241 | /** | ||
| 242 | * Removes a directory at path. | ||
| 243 | * | ||
| 244 | * Failures occur when: | ||
| 245 | * - Input path is not valid | ||
| 246 | * - Filesystem object at path is not a directory | ||
| 247 | * - The given directory is not empty | ||
| 248 | * - Filesystem at path is read only | ||
| 249 | * | ||
| 250 | * @param path Filesystem path | ||
| 251 | * | ||
| 252 | * @returns True if directory removal succeeds or directory does not exist, false otherwise. | ||
| 253 | */ | ||
| 254 | [[nodiscard]] bool RemoveDir(const std::filesystem::path& path); | ||
| 255 | |||
| 256 | #ifdef _WIN32 | ||
| 257 | template <typename Path> | ||
| 258 | [[nodiscard]] bool RemoveDir(const Path& path) { | ||
| 259 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 260 | return RemoveDir(ToU8String(path)); | ||
| 261 | } else { | ||
| 262 | return RemoveDir(std::filesystem::path{path}); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | #endif | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Removes all the contents within the given directory and removes the directory itself. | ||
| 269 | * | ||
| 270 | * Failures occur when: | ||
| 271 | * - Input path is not valid | ||
| 272 | * - Filesystem object at path is not a directory | ||
| 273 | * - Filesystem at path is read only | ||
| 274 | * | ||
| 275 | * @param path Filesystem path | ||
| 276 | * | ||
| 277 | * @returns True if the directory and all of its contents are removed successfully, false otherwise. | ||
| 278 | */ | ||
| 279 | [[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); | ||
| 280 | |||
| 281 | #ifdef _WIN32 | ||
| 282 | template <typename Path> | ||
| 283 | [[nodiscard]] bool RemoveDirRecursively(const Path& path) { | ||
| 284 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 285 | return RemoveDirRecursively(ToU8String(path)); | ||
| 286 | } else { | ||
| 287 | return RemoveDirRecursively(std::filesystem::path{path}); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | #endif | ||
| 291 | |||
| 292 | /** | ||
| 293 | * Removes all the contents within the given directory without removing the directory itself. | ||
| 294 | * | ||
| 295 | * Failures occur when: | ||
| 296 | * - Input path is not valid | ||
| 297 | * - Filesystem object at path is not a directory | ||
| 298 | * - Filesystem at path is read only | ||
| 299 | * | ||
| 300 | * @param path Filesystem path | ||
| 301 | * | ||
| 302 | * @returns True if all of the directory's contents are removed successfully, false otherwise. | ||
| 303 | */ | ||
| 304 | [[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); | ||
| 305 | |||
| 306 | #ifdef _WIN32 | ||
| 307 | template <typename Path> | ||
| 308 | [[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { | ||
| 309 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 310 | return RemoveDirContentsRecursively(ToU8String(path)); | ||
| 311 | } else { | ||
| 312 | return RemoveDirContentsRecursively(std::filesystem::path{path}); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | #endif | ||
| 316 | |||
| 317 | /** | ||
| 318 | * Renames a directory from old_path to new_path. | ||
| 319 | * | ||
| 320 | * Failures occur when: | ||
| 321 | * - One or both input path(s) is not valid | ||
| 322 | * - Filesystem object at old_path does not exist | ||
| 323 | * - Filesystem object at old_path is not a directory | ||
| 324 | * - Filesystem object at new_path exists | ||
| 325 | * - Filesystem at either path is read only | ||
| 326 | * | ||
| 327 | * @param old_path Old filesystem path | ||
| 328 | * @param new_path New filesystem path | ||
| 329 | * | ||
| 330 | * @returns True if directory rename succeeds, false otherwise. | ||
| 331 | */ | ||
| 332 | [[nodiscard]] bool RenameDir(const std::filesystem::path& old_path, | ||
| 333 | const std::filesystem::path& new_path); | ||
| 334 | |||
| 335 | #ifdef _WIN32 | ||
| 336 | template <typename Path1, typename Path2> | ||
| 337 | [[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) { | ||
| 338 | using ValueType1 = typename Path1::value_type; | ||
| 339 | using ValueType2 = typename Path2::value_type; | ||
| 340 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 341 | return RenameDir(ToU8String(old_path), ToU8String(new_path)); | ||
| 342 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 343 | return RenameDir(ToU8String(old_path), new_path); | ||
| 344 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 345 | return RenameDir(old_path, ToU8String(new_path)); | ||
| 346 | } else { | ||
| 347 | return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path}); | ||
| 348 | } | ||
| 349 | } | ||
| 350 | #endif | ||
| 351 | |||
| 352 | /** | ||
| 353 | * Iterates over the directory entries of a given directory. | ||
| 354 | * This does not iterate over the sub-directories of the given directory. | ||
| 355 | * The DirEntryCallable callback is called for each visited directory entry. | ||
| 356 | * A filter can be set to control which directory entries are visited based on their type. | ||
| 357 | * By default, both files and directories are visited. | ||
| 358 | * If the callback returns false or there is an error, the iteration is immediately halted. | ||
| 359 | * | ||
| 360 | * Failures occur when: | ||
| 361 | * - Input path is not valid | ||
| 362 | * - Filesystem object at path is not a directory | ||
| 363 | * | ||
| 364 | * @param path Filesystem path | ||
| 365 | * @param callback Callback to be called for each visited directory entry | ||
| 366 | * @param filter Directory entry type filter | ||
| 367 | */ | ||
| 368 | void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, | ||
| 369 | DirEntryFilter filter = DirEntryFilter::All); | ||
| 370 | |||
| 371 | #ifdef _WIN32 | ||
| 372 | template <typename Path> | ||
| 373 | void IterateDirEntries(const Path& path, const DirEntryCallable& callback, | ||
| 374 | DirEntryFilter filter = DirEntryFilter::All) { | ||
| 375 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 376 | IterateDirEntries(ToU8String(path), callback, filter); | ||
| 377 | } else { | ||
| 378 | IterateDirEntries(std::filesystem::path{path}, callback, filter); | ||
| 379 | } | ||
| 380 | } | ||
| 381 | #endif | ||
| 382 | |||
| 383 | /** | ||
| 384 | * Iterates over the directory entries of a given directory and its sub-directories. | ||
| 385 | * The DirEntryCallable callback is called for each visited directory entry. | ||
| 386 | * A filter can be set to control which directory entries are visited based on their type. | ||
| 387 | * By default, both files and directories are visited. | ||
| 388 | * If the callback returns false or there is an error, the iteration is immediately halted. | ||
| 389 | * | ||
| 390 | * Failures occur when: | ||
| 391 | * - Input path is not valid | ||
| 392 | * - Filesystem object at path does not exist | ||
| 393 | * - Filesystem object at path is not a directory | ||
| 394 | * | ||
| 395 | * @param path Filesystem path | ||
| 396 | * @param callback Callback to be called for each visited directory entry | ||
| 397 | * @param filter Directory entry type filter | ||
| 398 | */ | ||
| 399 | void IterateDirEntriesRecursively(const std::filesystem::path& path, | ||
| 400 | const DirEntryCallable& callback, | ||
| 401 | DirEntryFilter filter = DirEntryFilter::All); | ||
| 402 | |||
| 403 | #ifdef _WIN32 | ||
| 404 | template <typename Path> | ||
| 405 | void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback, | ||
| 406 | DirEntryFilter filter = DirEntryFilter::All) { | ||
| 407 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 408 | IterateDirEntriesRecursively(ToU8String(path), callback, filter); | ||
| 409 | } else { | ||
| 410 | IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | #endif | ||
| 414 | |||
| 415 | // Generic Filesystem Operations | ||
| 416 | |||
| 417 | /** | ||
| 418 | * Returns whether a filesystem object at path exists. | ||
| 419 | * | ||
| 420 | * @param path Filesystem path | ||
| 421 | * | ||
| 422 | * @returns True if a filesystem object at path exists, false otherwise. | ||
| 423 | */ | ||
| 424 | [[nodiscard]] bool Exists(const std::filesystem::path& path); | ||
| 425 | |||
| 426 | #ifdef _WIN32 | ||
| 427 | template <typename Path> | ||
| 428 | [[nodiscard]] bool Exists(const Path& path) { | ||
| 429 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 430 | return Exists(ToU8String(path)); | ||
| 431 | } else { | ||
| 432 | return Exists(std::filesystem::path{path}); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | #endif | ||
| 436 | |||
| 437 | /** | ||
| 438 | * Returns whether a filesystem object at path is a file. | ||
| 439 | * | ||
| 440 | * @param path Filesystem path | ||
| 441 | * | ||
| 442 | * @returns True if a filesystem object at path is a file, false otherwise. | ||
| 443 | */ | ||
| 444 | [[nodiscard]] bool IsFile(const std::filesystem::path& path); | ||
| 445 | |||
| 446 | #ifdef _WIN32 | ||
| 447 | template <typename Path> | ||
| 448 | [[nodiscard]] bool IsFile(const Path& path) { | ||
| 449 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 450 | return IsFile(ToU8String(path)); | ||
| 451 | } else { | ||
| 452 | return IsFile(std::filesystem::path{path}); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | #endif | ||
| 456 | |||
| 457 | /** | ||
| 458 | * Returns whether a filesystem object at path is a directory. | ||
| 459 | * | ||
| 460 | * @param path Filesystem path | ||
| 461 | * | ||
| 462 | * @returns True if a filesystem object at path is a directory, false otherwise. | ||
| 463 | */ | ||
| 464 | [[nodiscard]] bool IsDir(const std::filesystem::path& path); | ||
| 465 | |||
| 466 | #ifdef _WIN32 | ||
| 467 | template <typename Path> | ||
| 468 | [[nodiscard]] bool IsDir(const Path& path) { | ||
| 469 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 470 | return IsDir(ToU8String(path)); | ||
| 471 | } else { | ||
| 472 | return IsDir(std::filesystem::path{path}); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | #endif | ||
| 476 | |||
| 477 | /** | ||
| 478 | * Gets the current working directory. | ||
| 479 | * | ||
| 480 | * @returns The current working directory. Returns an empty path on failure. | ||
| 481 | */ | ||
| 482 | [[nodiscard]] std::filesystem::path GetCurrentDir(); | ||
| 483 | |||
| 484 | /** | ||
| 485 | * Sets the current working directory to path. | ||
| 486 | * | ||
| 487 | * @returns True if the current working directory is successfully set, false otherwise. | ||
| 488 | */ | ||
| 489 | [[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path); | ||
| 490 | |||
| 491 | #ifdef _WIN32 | ||
| 492 | template <typename Path> | ||
| 493 | [[nodiscard]] bool SetCurrentDir(const Path& path) { | ||
| 494 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 495 | return SetCurrentDir(ToU8String(path)); | ||
| 496 | } else { | ||
| 497 | return SetCurrentDir(std::filesystem::path{path}); | ||
| 498 | } | ||
| 499 | } | ||
| 500 | #endif | ||
| 501 | |||
| 502 | /** | ||
| 503 | * Gets the entry type of the filesystem object at path. | ||
| 504 | * | ||
| 505 | * @param path Filesystem path | ||
| 506 | * | ||
| 507 | * @returns The entry type of the filesystem object. Returns file_type::not_found on failure. | ||
| 508 | */ | ||
| 509 | [[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path); | ||
| 510 | |||
| 511 | #ifdef _WIN32 | ||
| 512 | template <typename Path> | ||
| 513 | [[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) { | ||
| 514 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 515 | return GetEntryType(ToU8String(path)); | ||
| 516 | } else { | ||
| 517 | return GetEntryType(std::filesystem::path{path}); | ||
| 518 | } | ||
| 519 | } | ||
| 520 | #endif | ||
| 521 | |||
| 522 | /** | ||
| 523 | * Gets the size of the filesystem object at path. | ||
| 524 | * | ||
| 525 | * @param path Filesystem path | ||
| 526 | * | ||
| 527 | * @returns The size in bytes of the filesystem object. Returns 0 on failure. | ||
| 528 | */ | ||
| 529 | [[nodiscard]] u64 GetSize(const std::filesystem::path& path); | ||
| 530 | |||
| 531 | #ifdef _WIN32 | ||
| 532 | template <typename Path> | ||
| 533 | [[nodiscard]] u64 GetSize(const Path& path) { | ||
| 534 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 535 | return GetSize(ToU8String(path)); | ||
| 536 | } else { | ||
| 537 | return GetSize(std::filesystem::path{path}); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | #endif | ||
| 541 | |||
| 542 | /** | ||
| 543 | * Gets the free space size of the filesystem at path. | ||
| 544 | * | ||
| 545 | * @param path Filesystem path | ||
| 546 | * | ||
| 547 | * @returns The free space size in bytes of the filesystem at path. Returns 0 on failure. | ||
| 548 | */ | ||
| 549 | [[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path); | ||
| 550 | |||
| 551 | #ifdef _WIN32 | ||
| 552 | template <typename Path> | ||
| 553 | [[nodiscard]] u64 GetFreeSpaceSize(const Path& path) { | ||
| 554 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 555 | return GetFreeSpaceSize(ToU8String(path)); | ||
| 556 | } else { | ||
| 557 | return GetFreeSpaceSize(std::filesystem::path{path}); | ||
| 558 | } | ||
| 559 | } | ||
| 560 | #endif | ||
| 561 | |||
| 562 | /** | ||
| 563 | * Gets the total capacity of the filesystem at path. | ||
| 564 | * | ||
| 565 | * @param path Filesystem path | ||
| 566 | * | ||
| 567 | * @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure. | ||
| 568 | */ | ||
| 569 | [[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path); | ||
| 570 | |||
| 571 | #ifdef _WIN32 | ||
| 572 | template <typename Path> | ||
| 573 | [[nodiscard]] u64 GetTotalSpaceSize(const Path& path) { | ||
| 574 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 575 | return GetTotalSpaceSize(ToU8String(path)); | ||
| 576 | } else { | ||
| 577 | return GetTotalSpaceSize(std::filesystem::path{path}); | ||
| 578 | } | ||
| 579 | } | ||
| 580 | #endif | ||
| 581 | |||
| 582 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h new file mode 100644 index 000000000..b32614797 --- /dev/null +++ b/src/common/fs/fs_paths.h | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | // yuzu data directories | ||
| 8 | |||
| 9 | #define YUZU_DIR "yuzu" | ||
| 10 | #define PORTABLE_DIR "user" | ||
| 11 | |||
| 12 | // Sub-directories contained within a yuzu data directory | ||
| 13 | |||
| 14 | #define CACHE_DIR "cache" | ||
| 15 | #define CONFIG_DIR "config" | ||
| 16 | #define DUMP_DIR "dump" | ||
| 17 | #define KEYS_DIR "keys" | ||
| 18 | #define LOAD_DIR "load" | ||
| 19 | #define LOG_DIR "log" | ||
| 20 | #define NAND_DIR "nand" | ||
| 21 | #define SCREENSHOTS_DIR "screenshots" | ||
| 22 | #define SDMC_DIR "sdmc" | ||
| 23 | #define SHADER_DIR "shader" | ||
| 24 | |||
| 25 | // yuzu-specific files | ||
| 26 | |||
| 27 | #define LOG_FILE "yuzu_log.txt" | ||
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h new file mode 100644 index 000000000..089980aee --- /dev/null +++ b/src/common/fs/fs_types.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <functional> | ||
| 8 | |||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Common::FS { | ||
| 13 | |||
| 14 | enum class FileAccessMode { | ||
| 15 | /** | ||
| 16 | * If the file at path exists, it opens the file for reading. | ||
| 17 | * If the file at path does not exist, it fails to open the file. | ||
| 18 | */ | ||
| 19 | Read = 1 << 0, | ||
| 20 | /** | ||
| 21 | * If the file at path exists, the existing contents of the file are erased. | ||
| 22 | * The empty file is then opened for writing. | ||
| 23 | * If the file at path does not exist, it creates and opens a new empty file for writing. | ||
| 24 | */ | ||
| 25 | Write = 1 << 1, | ||
| 26 | /** | ||
| 27 | * If the file at path exists, it opens the file for reading and writing. | ||
| 28 | * If the file at path does not exist, it fails to open the file. | ||
| 29 | */ | ||
| 30 | ReadWrite = Read | Write, | ||
| 31 | /** | ||
| 32 | * If the file at path exists, it opens the file for appending. | ||
| 33 | * If the file at path does not exist, it creates and opens a new empty file for appending. | ||
| 34 | */ | ||
| 35 | Append = 1 << 2, | ||
| 36 | /** | ||
| 37 | * If the file at path exists, it opens the file for both reading and appending. | ||
| 38 | * If the file at path does not exist, it creates and opens a new empty file for both | ||
| 39 | * reading and appending. | ||
| 40 | */ | ||
| 41 | ReadAppend = Read | Append, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class FileType { | ||
| 45 | BinaryFile, | ||
| 46 | TextFile, | ||
| 47 | }; | ||
| 48 | |||
| 49 | enum class FileShareFlag { | ||
| 50 | ShareNone, // Provides exclusive access to the file. | ||
| 51 | ShareReadOnly, // Provides read only shared access to the file. | ||
| 52 | ShareWriteOnly, // Provides write only shared access to the file. | ||
| 53 | ShareReadWrite, // Provides read and write shared access to the file. | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class DirEntryFilter { | ||
| 57 | File = 1 << 0, | ||
| 58 | Directory = 1 << 1, | ||
| 59 | All = File | Directory, | ||
| 60 | }; | ||
| 61 | DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); | ||
| 62 | |||
| 63 | /** | ||
| 64 | * A callback function which takes in the path of a directory entry. | ||
| 65 | * | ||
| 66 | * @param path The path of a directory entry | ||
| 67 | * | ||
| 68 | * @returns A boolean value. | ||
| 69 | * Return true to indicate whether the callback is successful, false otherwise. | ||
| 70 | */ | ||
| 71 | using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; | ||
| 72 | |||
| 73 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_util.cpp b/src/common/fs/fs_util.cpp new file mode 100644 index 000000000..0ddfc3131 --- /dev/null +++ b/src/common/fs/fs_util.cpp | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/fs/fs_util.h" | ||
| 6 | |||
| 7 | namespace Common::FS { | ||
| 8 | |||
| 9 | std::u8string ToU8String(std::string_view utf8_string) { | ||
| 10 | return std::u8string{utf8_string.begin(), utf8_string.end()}; | ||
| 11 | } | ||
| 12 | |||
| 13 | } // namespace Common::FS | ||
diff --git a/src/common/fs/fs_util.h b/src/common/fs/fs_util.h new file mode 100644 index 000000000..951df53b6 --- /dev/null +++ b/src/common/fs/fs_util.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <concepts> | ||
| 8 | #include <string> | ||
| 9 | #include <string_view> | ||
| 10 | |||
| 11 | namespace Common::FS { | ||
| 12 | |||
| 13 | template <typename T> | ||
| 14 | concept IsChar = std::same_as<T, char>; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Converts a UTF-8 encoded std::string or std::string_view to a std::u8string. | ||
| 18 | * | ||
| 19 | * @param utf8_string UTF-8 encoded string | ||
| 20 | * | ||
| 21 | * @returns UTF-8 encoded std::u8string. | ||
| 22 | */ | ||
| 23 | [[nodiscard]] std::u8string ToU8String(std::string_view utf8_string); | ||
| 24 | |||
| 25 | } // namespace Common::FS | ||
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp new file mode 100644 index 000000000..8b732a21c --- /dev/null +++ b/src/common/fs/path_util.cpp | |||
| @@ -0,0 +1,432 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <unordered_map> | ||
| 7 | |||
| 8 | #include "common/fs/fs.h" | ||
| 9 | #include "common/fs/fs_paths.h" | ||
| 10 | #include "common/fs/path_util.h" | ||
| 11 | #include "common/logging/log.h" | ||
| 12 | |||
| 13 | #ifdef _WIN32 | ||
| 14 | #include <shlobj.h> // Used in GetExeDirectory() | ||
| 15 | #else | ||
| 16 | #include <cstdlib> // Used in Get(Home/Data)Directory() | ||
| 17 | #include <pwd.h> // Used in GetHomeDirectory() | ||
| 18 | #include <sys/types.h> // Used in GetHomeDirectory() | ||
| 19 | #include <unistd.h> // Used in GetDataDirectory() | ||
| 20 | #endif | ||
| 21 | |||
| 22 | #ifdef __APPLE__ | ||
| 23 | #include <sys/param.h> // Used in GetBundleDirectory() | ||
| 24 | |||
| 25 | // CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just | ||
| 26 | // ignore them if we're not using clang. The macro is only used to prevent linking against | ||
| 27 | // functions that don't exist on older versions of macOS, and the worst case scenario is a linker | ||
| 28 | // error, so this is perfectly safe, just inconvenient. | ||
| 29 | #ifndef __clang__ | ||
| 30 | #define availability(...) | ||
| 31 | #endif | ||
| 32 | #include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory() | ||
| 33 | #include <CoreFoundation/CFString.h> // Used in GetBundleDirectory() | ||
| 34 | #include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory() | ||
| 35 | #ifdef availability | ||
| 36 | #undef availability | ||
| 37 | #endif | ||
| 38 | #endif | ||
| 39 | |||
| 40 | #ifndef MAX_PATH | ||
| 41 | #ifdef _WIN32 | ||
| 42 | // This is the maximum number of UTF-16 code units permissible in Windows file paths | ||
| 43 | #define MAX_PATH 260 | ||
| 44 | #else | ||
| 45 | // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths | ||
| 46 | #define MAX_PATH 1024 | ||
| 47 | #endif | ||
| 48 | #endif | ||
| 49 | |||
| 50 | namespace Common::FS { | ||
| 51 | |||
| 52 | namespace fs = std::filesystem; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * The PathManagerImpl is a singleton allowing to manage the mapping of | ||
| 56 | * YuzuPath enums to real filesystem paths. | ||
| 57 | * This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl. | ||
| 58 | * These are used by GetYuzuPath and SetYuzuPath respectively to get or modify | ||
| 59 | * the path mapped by the YuzuPath enum. | ||
| 60 | */ | ||
| 61 | class PathManagerImpl { | ||
| 62 | public: | ||
| 63 | static PathManagerImpl& GetInstance() { | ||
| 64 | static PathManagerImpl path_manager_impl; | ||
| 65 | |||
| 66 | return path_manager_impl; | ||
| 67 | } | ||
| 68 | |||
| 69 | PathManagerImpl(const PathManagerImpl&) = delete; | ||
| 70 | PathManagerImpl& operator=(const PathManagerImpl&) = delete; | ||
| 71 | |||
| 72 | PathManagerImpl(PathManagerImpl&&) = delete; | ||
| 73 | PathManagerImpl& operator=(PathManagerImpl&&) = delete; | ||
| 74 | |||
| 75 | [[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) { | ||
| 76 | return yuzu_paths.at(yuzu_path); | ||
| 77 | } | ||
| 78 | |||
| 79 | void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 80 | yuzu_paths.insert_or_assign(yuzu_path, new_path); | ||
| 81 | } | ||
| 82 | |||
| 83 | private: | ||
| 84 | PathManagerImpl() { | ||
| 85 | #ifdef _WIN32 | ||
| 86 | auto yuzu_path = GetExeDirectory() / PORTABLE_DIR; | ||
| 87 | |||
| 88 | if (!IsDir(yuzu_path)) { | ||
| 89 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; | ||
| 90 | } | ||
| 91 | |||
| 92 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 93 | GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); | ||
| 94 | GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); | ||
| 95 | #else | ||
| 96 | auto yuzu_path = GetCurrentDir() / PORTABLE_DIR; | ||
| 97 | |||
| 98 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { | ||
| 99 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 100 | GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); | ||
| 101 | GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); | ||
| 102 | } else { | ||
| 103 | yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR; | ||
| 104 | |||
| 105 | GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); | ||
| 106 | GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR); | ||
| 107 | GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR); | ||
| 108 | } | ||
| 109 | #endif | ||
| 110 | |||
| 111 | GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); | ||
| 112 | GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR); | ||
| 113 | GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); | ||
| 114 | GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); | ||
| 115 | GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); | ||
| 116 | GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); | ||
| 117 | GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); | ||
| 118 | GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); | ||
| 119 | } | ||
| 120 | |||
| 121 | ~PathManagerImpl() = default; | ||
| 122 | |||
| 123 | void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 124 | void(FS::CreateDir(new_path)); | ||
| 125 | |||
| 126 | SetYuzuPathImpl(yuzu_path, new_path); | ||
| 127 | } | ||
| 128 | |||
| 129 | std::unordered_map<YuzuPath, fs::path> yuzu_paths; | ||
| 130 | }; | ||
| 131 | |||
| 132 | std::string PathToUTF8String(const fs::path& path) { | ||
| 133 | const auto utf8_string = path.u8string(); | ||
| 134 | |||
| 135 | return std::string{utf8_string.begin(), utf8_string.end()}; | ||
| 136 | } | ||
| 137 | |||
| 138 | bool ValidatePath(const fs::path& path) { | ||
| 139 | if (path.empty()) { | ||
| 140 | LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path)); | ||
| 141 | return false; | ||
| 142 | } | ||
| 143 | |||
| 144 | #ifdef _WIN32 | ||
| 145 | if (path.u16string().size() >= MAX_PATH) { | ||
| 146 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | #else | ||
| 150 | if (path.u8string().size() >= MAX_PATH) { | ||
| 151 | LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); | ||
| 152 | return false; | ||
| 153 | } | ||
| 154 | #endif | ||
| 155 | |||
| 156 | return true; | ||
| 157 | } | ||
| 158 | |||
| 159 | fs::path ConcatPath(const fs::path& first, const fs::path& second) { | ||
| 160 | const bool second_has_dir_sep = IsDirSeparator(second.u8string().front()); | ||
| 161 | |||
| 162 | if (!second_has_dir_sep) { | ||
| 163 | return (first / second).lexically_normal(); | ||
| 164 | } | ||
| 165 | |||
| 166 | fs::path concat_path = first; | ||
| 167 | concat_path += second; | ||
| 168 | |||
| 169 | return concat_path.lexically_normal(); | ||
| 170 | } | ||
| 171 | |||
| 172 | fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) { | ||
| 173 | const auto concatenated_path = ConcatPath(base, offset); | ||
| 174 | |||
| 175 | if (!IsPathSandboxed(base, concatenated_path)) { | ||
| 176 | return base; | ||
| 177 | } | ||
| 178 | |||
| 179 | return concatenated_path; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool IsPathSandboxed(const fs::path& base, const fs::path& path) { | ||
| 183 | const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string(); | ||
| 184 | const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string(); | ||
| 185 | |||
| 186 | if (path_string.size() < base_string.size()) { | ||
| 187 | return false; | ||
| 188 | } | ||
| 189 | |||
| 190 | return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool IsDirSeparator(char character) { | ||
| 194 | return character == '/' || character == '\\'; | ||
| 195 | } | ||
| 196 | |||
| 197 | bool IsDirSeparator(char8_t character) { | ||
| 198 | return character == u8'/' || character == u8'\\'; | ||
| 199 | } | ||
| 200 | |||
| 201 | fs::path RemoveTrailingSeparators(const fs::path& path) { | ||
| 202 | if (path.empty()) { | ||
| 203 | return path; | ||
| 204 | } | ||
| 205 | |||
| 206 | auto string_path = path.u8string(); | ||
| 207 | |||
| 208 | while (IsDirSeparator(string_path.back())) { | ||
| 209 | string_path.pop_back(); | ||
| 210 | } | ||
| 211 | |||
| 212 | return fs::path{string_path}; | ||
| 213 | } | ||
| 214 | |||
| 215 | const fs::path& GetYuzuPath(YuzuPath yuzu_path) { | ||
| 216 | return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); | ||
| 217 | } | ||
| 218 | |||
| 219 | std::string GetYuzuPathString(YuzuPath yuzu_path) { | ||
| 220 | return PathToUTF8String(GetYuzuPath(yuzu_path)); | ||
| 221 | } | ||
| 222 | |||
| 223 | void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { | ||
| 224 | if (!FS::IsDir(new_path)) { | ||
| 225 | LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory", | ||
| 226 | PathToUTF8String(new_path)); | ||
| 227 | return; | ||
| 228 | } | ||
| 229 | |||
| 230 | PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path); | ||
| 231 | } | ||
| 232 | |||
| 233 | #ifdef _WIN32 | ||
| 234 | |||
| 235 | fs::path GetExeDirectory() { | ||
| 236 | wchar_t exe_path[MAX_PATH]; | ||
| 237 | |||
| 238 | GetModuleFileNameW(nullptr, exe_path, MAX_PATH); | ||
| 239 | |||
| 240 | if (!exe_path) { | ||
| 241 | LOG_ERROR(Common_Filesystem, | ||
| 242 | "Failed to get the path to the executable of the current process"); | ||
| 243 | } | ||
| 244 | |||
| 245 | return fs::path{exe_path}.parent_path(); | ||
| 246 | } | ||
| 247 | |||
| 248 | fs::path GetAppDataRoamingDirectory() { | ||
| 249 | PWSTR appdata_roaming_path = nullptr; | ||
| 250 | |||
| 251 | SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path); | ||
| 252 | |||
| 253 | auto fs_appdata_roaming_path = fs::path{appdata_roaming_path}; | ||
| 254 | |||
| 255 | CoTaskMemFree(appdata_roaming_path); | ||
| 256 | |||
| 257 | if (fs_appdata_roaming_path.empty()) { | ||
| 258 | LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory"); | ||
| 259 | } | ||
| 260 | |||
| 261 | return fs_appdata_roaming_path; | ||
| 262 | } | ||
| 263 | |||
| 264 | #else | ||
| 265 | |||
| 266 | fs::path GetHomeDirectory() { | ||
| 267 | const char* home_env_var = getenv("HOME"); | ||
| 268 | |||
| 269 | if (home_env_var) { | ||
| 270 | return fs::path{home_env_var}; | ||
| 271 | } | ||
| 272 | |||
| 273 | LOG_INFO(Common_Filesystem, | ||
| 274 | "$HOME is not defined in the environment variables, " | ||
| 275 | "attempting to query passwd to get the home path of the current user"); | ||
| 276 | |||
| 277 | const auto* pw = getpwuid(getuid()); | ||
| 278 | |||
| 279 | if (!pw) { | ||
| 280 | LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user"); | ||
| 281 | return {}; | ||
| 282 | } | ||
| 283 | |||
| 284 | return fs::path{pw->pw_dir}; | ||
| 285 | } | ||
| 286 | |||
| 287 | fs::path GetDataDirectory(const std::string& env_name) { | ||
| 288 | const char* data_env_var = getenv(env_name.c_str()); | ||
| 289 | |||
| 290 | if (data_env_var) { | ||
| 291 | return fs::path{data_env_var}; | ||
| 292 | } | ||
| 293 | |||
| 294 | if (env_name == "XDG_DATA_HOME") { | ||
| 295 | return GetHomeDirectory() / ".local/share"; | ||
| 296 | } else if (env_name == "XDG_CACHE_HOME") { | ||
| 297 | return GetHomeDirectory() / ".cache"; | ||
| 298 | } else if (env_name == "XDG_CONFIG_HOME") { | ||
| 299 | return GetHomeDirectory() / ".config"; | ||
| 300 | } | ||
| 301 | |||
| 302 | return {}; | ||
| 303 | } | ||
| 304 | |||
| 305 | #endif | ||
| 306 | |||
| 307 | #ifdef __APPLE__ | ||
| 308 | |||
| 309 | fs::path GetBundleDirectory() { | ||
| 310 | char app_bundle_path[MAXPATHLEN]; | ||
| 311 | |||
| 312 | // Get the main bundle for the app | ||
| 313 | CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle()); | ||
| 314 | CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle); | ||
| 315 | |||
| 316 | CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path)); | ||
| 317 | |||
| 318 | CFRelease(bundle_ref); | ||
| 319 | CFRelease(bundle_path); | ||
| 320 | |||
| 321 | return fs::path{app_bundle_path}; | ||
| 322 | } | ||
| 323 | |||
| 324 | #endif | ||
| 325 | |||
| 326 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | ||
| 327 | |||
| 328 | std::string_view RemoveTrailingSlash(std::string_view path) { | ||
| 329 | if (path.empty()) { | ||
| 330 | return path; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (path.back() == '\\' || path.back() == '/') { | ||
| 334 | path.remove_suffix(1); | ||
| 335 | return path; | ||
| 336 | } | ||
| 337 | |||
| 338 | return path; | ||
| 339 | } | ||
| 340 | |||
| 341 | std::vector<std::string> SplitPathComponents(std::string_view filename) { | ||
| 342 | std::string copy(filename); | ||
| 343 | std::replace(copy.begin(), copy.end(), '\\', '/'); | ||
| 344 | std::vector<std::string> out; | ||
| 345 | |||
| 346 | std::stringstream stream(copy); | ||
| 347 | std::string item; | ||
| 348 | while (std::getline(stream, item, '/')) { | ||
| 349 | out.push_back(std::move(item)); | ||
| 350 | } | ||
| 351 | |||
| 352 | return out; | ||
| 353 | } | ||
| 354 | |||
| 355 | std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { | ||
| 356 | std::string path(path_); | ||
| 357 | char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; | ||
| 358 | char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; | ||
| 359 | |||
| 360 | if (directory_separator == DirectorySeparator::PlatformDefault) { | ||
| 361 | #ifdef _WIN32 | ||
| 362 | type1 = '/'; | ||
| 363 | type2 = '\\'; | ||
| 364 | #endif | ||
| 365 | } | ||
| 366 | |||
| 367 | std::replace(path.begin(), path.end(), type1, type2); | ||
| 368 | |||
| 369 | auto start = path.begin(); | ||
| 370 | #ifdef _WIN32 | ||
| 371 | // allow network paths which start with a double backslash (e.g. \\server\share) | ||
| 372 | if (start != path.end()) | ||
| 373 | ++start; | ||
| 374 | #endif | ||
| 375 | path.erase(std::unique(start, path.end(), | ||
| 376 | [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), | ||
| 377 | path.end()); | ||
| 378 | return std::string(RemoveTrailingSlash(path)); | ||
| 379 | } | ||
| 380 | |||
| 381 | std::string_view GetParentPath(std::string_view path) { | ||
| 382 | const auto name_bck_index = path.rfind('\\'); | ||
| 383 | const auto name_fwd_index = path.rfind('/'); | ||
| 384 | std::size_t name_index; | ||
| 385 | |||
| 386 | if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { | ||
| 387 | name_index = std::min(name_bck_index, name_fwd_index); | ||
| 388 | } else { | ||
| 389 | name_index = std::max(name_bck_index, name_fwd_index); | ||
| 390 | } | ||
| 391 | |||
| 392 | return path.substr(0, name_index); | ||
| 393 | } | ||
| 394 | |||
| 395 | std::string_view GetPathWithoutTop(std::string_view path) { | ||
| 396 | if (path.empty()) { | ||
| 397 | return path; | ||
| 398 | } | ||
| 399 | |||
| 400 | while (path[0] == '\\' || path[0] == '/') { | ||
| 401 | path.remove_prefix(1); | ||
| 402 | if (path.empty()) { | ||
| 403 | return path; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | const auto name_bck_index = path.find('\\'); | ||
| 408 | const auto name_fwd_index = path.find('/'); | ||
| 409 | return path.substr(std::min(name_bck_index, name_fwd_index) + 1); | ||
| 410 | } | ||
| 411 | |||
| 412 | std::string_view GetFilename(std::string_view path) { | ||
| 413 | const auto name_index = path.find_last_of("\\/"); | ||
| 414 | |||
| 415 | if (name_index == std::string_view::npos) { | ||
| 416 | return {}; | ||
| 417 | } | ||
| 418 | |||
| 419 | return path.substr(name_index + 1); | ||
| 420 | } | ||
| 421 | |||
| 422 | std::string_view GetExtensionFromFilename(std::string_view name) { | ||
| 423 | const std::size_t index = name.rfind('.'); | ||
| 424 | |||
| 425 | if (index == std::string_view::npos) { | ||
| 426 | return {}; | ||
| 427 | } | ||
| 428 | |||
| 429 | return name.substr(index + 1); | ||
| 430 | } | ||
| 431 | |||
| 432 | } // namespace Common::FS | ||
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h new file mode 100644 index 000000000..a9fadbceb --- /dev/null +++ b/src/common/fs/path_util.h | |||
| @@ -0,0 +1,309 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <filesystem> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/fs/fs_util.h" | ||
| 11 | |||
| 12 | namespace Common::FS { | ||
| 13 | |||
| 14 | enum class YuzuPath { | ||
| 15 | YuzuDir, // Where yuzu stores its data. | ||
| 16 | CacheDir, // Where cached filesystem data is stored. | ||
| 17 | ConfigDir, // Where config files are stored. | ||
| 18 | DumpDir, // Where dumped data is stored. | ||
| 19 | KeysDir, // Where key files are stored. | ||
| 20 | LoadDir, // Where cheat/mod files are stored. | ||
| 21 | LogDir, // Where log files are stored. | ||
| 22 | NANDDir, // Where the emulated NAND is stored. | ||
| 23 | ScreenshotsDir, // Where yuzu screenshots are stored. | ||
| 24 | SDMCDir, // Where the emulated SDMC is stored. | ||
| 25 | ShaderDir, // Where shaders are stored. | ||
| 26 | }; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Converts a filesystem path to a UTF-8 encoded std::string. | ||
| 30 | * | ||
| 31 | * @param path Filesystem path | ||
| 32 | * | ||
| 33 | * @returns UTF-8 encoded std::string. | ||
| 34 | */ | ||
| 35 | [[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Validates a given path. | ||
| 39 | * | ||
| 40 | * A given path is valid if it meets these conditions: | ||
| 41 | * - The path is not empty | ||
| 42 | * - The path is not too long | ||
| 43 | * | ||
| 44 | * @param path Filesystem path | ||
| 45 | * | ||
| 46 | * @returns True if the path is valid, false otherwise. | ||
| 47 | */ | ||
| 48 | [[nodiscard]] bool ValidatePath(const std::filesystem::path& path); | ||
| 49 | |||
| 50 | #ifdef _WIN32 | ||
| 51 | template <typename Path> | ||
| 52 | [[nodiscard]] bool ValidatePath(const Path& path) { | ||
| 53 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 54 | return ValidatePath(ToU8String(path)); | ||
| 55 | } else { | ||
| 56 | return ValidatePath(std::filesystem::path{path}); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | #endif | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Concatenates two filesystem paths together. | ||
| 63 | * | ||
| 64 | * This is needed since the following occurs when using std::filesystem::path's operator/: | ||
| 65 | * first: "/first/path" | ||
| 66 | * second: "/second/path" (Note that the second path has a directory separator in the front) | ||
| 67 | * first / second yields "/second/path" when the desired result is first/path/second/path | ||
| 68 | * | ||
| 69 | * @param first First filesystem path | ||
| 70 | * @param second Second filesystem path | ||
| 71 | * | ||
| 72 | * @returns A concatenated filesystem path. | ||
| 73 | */ | ||
| 74 | [[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first, | ||
| 75 | const std::filesystem::path& second); | ||
| 76 | |||
| 77 | #ifdef _WIN32 | ||
| 78 | template <typename Path1, typename Path2> | ||
| 79 | [[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) { | ||
| 80 | using ValueType1 = typename Path1::value_type; | ||
| 81 | using ValueType2 = typename Path2::value_type; | ||
| 82 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 83 | return ConcatPath(ToU8String(first), ToU8String(second)); | ||
| 84 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 85 | return ConcatPath(ToU8String(first), second); | ||
| 86 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 87 | return ConcatPath(first, ToU8String(second)); | ||
| 88 | } else { | ||
| 89 | return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second}); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | #endif | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Safe variant of ConcatPath that takes in a base path and an offset path from the given base path. | ||
| 96 | * | ||
| 97 | * If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path, | ||
| 98 | * this will return the concatenated path. Otherwise this will return the base path. | ||
| 99 | * | ||
| 100 | * @param base Base filesystem path | ||
| 101 | * @param offset Offset filesystem path | ||
| 102 | * | ||
| 103 | * @returns A concatenated filesystem path if it is within the base path, | ||
| 104 | * returns the base path otherwise. | ||
| 105 | */ | ||
| 106 | [[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base, | ||
| 107 | const std::filesystem::path& offset); | ||
| 108 | |||
| 109 | #ifdef _WIN32 | ||
| 110 | template <typename Path1, typename Path2> | ||
| 111 | [[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) { | ||
| 112 | using ValueType1 = typename Path1::value_type; | ||
| 113 | using ValueType2 = typename Path2::value_type; | ||
| 114 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 115 | return ConcatPathSafe(ToU8String(base), ToU8String(offset)); | ||
| 116 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 117 | return ConcatPathSafe(ToU8String(base), offset); | ||
| 118 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 119 | return ConcatPathSafe(base, ToU8String(offset)); | ||
| 120 | } else { | ||
| 121 | return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset}); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | #endif | ||
| 125 | |||
| 126 | /** | ||
| 127 | * Checks whether a given path is sandboxed within a given base path. | ||
| 128 | * | ||
| 129 | * @param base Base filesystem path | ||
| 130 | * @param path Filesystem path | ||
| 131 | * | ||
| 132 | * @returns True if the given path is sandboxed within the given base path, false otherwise. | ||
| 133 | */ | ||
| 134 | [[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base, | ||
| 135 | const std::filesystem::path& path); | ||
| 136 | |||
| 137 | #ifdef _WIN32 | ||
| 138 | template <typename Path1, typename Path2> | ||
| 139 | [[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) { | ||
| 140 | using ValueType1 = typename Path1::value_type; | ||
| 141 | using ValueType2 = typename Path2::value_type; | ||
| 142 | if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 143 | return IsPathSandboxed(ToU8String(base), ToU8String(path)); | ||
| 144 | } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { | ||
| 145 | return IsPathSandboxed(ToU8String(base), path); | ||
| 146 | } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { | ||
| 147 | return IsPathSandboxed(base, ToU8String(path)); | ||
| 148 | } else { | ||
| 149 | return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path}); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | #endif | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Checks if a character is a directory separator (either a forward slash or backslash). | ||
| 156 | * | ||
| 157 | * @param character Character | ||
| 158 | * | ||
| 159 | * @returns True if the character is a directory separator, false otherwise. | ||
| 160 | */ | ||
| 161 | [[nodiscard]] bool IsDirSeparator(char character); | ||
| 162 | |||
| 163 | /** | ||
| 164 | * Checks if a character is a directory separator (either a forward slash or backslash). | ||
| 165 | * | ||
| 166 | * @param character Character | ||
| 167 | * | ||
| 168 | * @returns True if the character is a directory separator, false otherwise. | ||
| 169 | */ | ||
| 170 | [[nodiscard]] bool IsDirSeparator(char8_t character); | ||
| 171 | |||
| 172 | /** | ||
| 173 | * Removes any trailing directory separators if they exist in the given path. | ||
| 174 | * | ||
| 175 | * @param path Filesystem path | ||
| 176 | * | ||
| 177 | * @returns The filesystem path without any trailing directory separators. | ||
| 178 | */ | ||
| 179 | [[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path); | ||
| 180 | |||
| 181 | #ifdef _WIN32 | ||
| 182 | template <typename Path> | ||
| 183 | [[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) { | ||
| 184 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 185 | return RemoveTrailingSeparators(ToU8String(path)); | ||
| 186 | } else { | ||
| 187 | return RemoveTrailingSeparators(std::filesystem::path{path}); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | #endif | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Gets the filesystem path associated with the YuzuPath enum. | ||
| 194 | * | ||
| 195 | * @param yuzu_path YuzuPath enum | ||
| 196 | * | ||
| 197 | * @returns The filesystem path associated with the YuzuPath enum. | ||
| 198 | */ | ||
| 199 | [[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path); | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. | ||
| 203 | * | ||
| 204 | * @param yuzu_path YuzuPath enum | ||
| 205 | * | ||
| 206 | * @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. | ||
| 207 | */ | ||
| 208 | [[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path); | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Sets a new filesystem path associated with the YuzuPath enum. | ||
| 212 | * If the filesystem object at new_path is not a directory, this function will not do anything. | ||
| 213 | * | ||
| 214 | * @param yuzu_path YuzuPath enum | ||
| 215 | * @param new_path New filesystem path | ||
| 216 | */ | ||
| 217 | void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path); | ||
| 218 | |||
| 219 | #ifdef _WIN32 | ||
| 220 | template <typename Path> | ||
| 221 | [[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { | ||
| 222 | if constexpr (IsChar<typename Path::value_type>) { | ||
| 223 | SetYuzuPath(yuzu_path, ToU8String(new_path)); | ||
| 224 | } else { | ||
| 225 | SetYuzuPath(yuzu_path, std::filesystem::path{new_path}); | ||
| 226 | } | ||
| 227 | } | ||
| 228 | #endif | ||
| 229 | |||
| 230 | #ifdef _WIN32 | ||
| 231 | |||
| 232 | /** | ||
| 233 | * Gets the path of the directory containing the executable of the current process. | ||
| 234 | * | ||
| 235 | * @returns The path of the directory containing the executable of the current process. | ||
| 236 | */ | ||
| 237 | [[nodiscard]] std::filesystem::path GetExeDirectory(); | ||
| 238 | |||
| 239 | /** | ||
| 240 | * Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming). | ||
| 241 | * | ||
| 242 | * @returns The path of the current user's %APPDATA% directory. | ||
| 243 | */ | ||
| 244 | [[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory(); | ||
| 245 | |||
| 246 | #else | ||
| 247 | |||
| 248 | /** | ||
| 249 | * Gets the path of the directory specified by the #HOME environment variable. | ||
| 250 | * If $HOME is not defined, it will attempt to query the user database in passwd instead. | ||
| 251 | * | ||
| 252 | * @returns The path of the current user's home directory. | ||
| 253 | */ | ||
| 254 | [[nodiscard]] std::filesystem::path GetHomeDirectory(); | ||
| 255 | |||
| 256 | /** | ||
| 257 | * Gets the relevant paths for yuzu to store its data based on the given XDG environment variable. | ||
| 258 | * See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html | ||
| 259 | * Defaults to $HOME/.local/share for main application data, | ||
| 260 | * $HOME/.cache for cached data, and $HOME/.config for configuration files. | ||
| 261 | * | ||
| 262 | * @param env_name XDG environment variable name | ||
| 263 | * | ||
| 264 | * @returns The path where yuzu should store its data. | ||
| 265 | */ | ||
| 266 | [[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name); | ||
| 267 | |||
| 268 | #endif | ||
| 269 | |||
| 270 | #ifdef __APPLE__ | ||
| 271 | |||
| 272 | [[nodiscard]] std::filesystem::path GetBundleDirectory(); | ||
| 273 | |||
| 274 | #endif | ||
| 275 | |||
| 276 | // vvvvvvvvvv Deprecated vvvvvvvvvv // | ||
| 277 | |||
| 278 | // Removes the final '/' or '\' if one exists | ||
| 279 | [[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); | ||
| 280 | |||
| 281 | enum class DirectorySeparator { | ||
| 282 | ForwardSlash, | ||
| 283 | BackwardSlash, | ||
| 284 | PlatformDefault, | ||
| 285 | }; | ||
| 286 | |||
| 287 | // Splits the path on '/' or '\' and put the components into a vector | ||
| 288 | // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } | ||
| 289 | [[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); | ||
| 290 | |||
| 291 | // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' | ||
| 292 | // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows | ||
| 293 | [[nodiscard]] std::string SanitizePath( | ||
| 294 | std::string_view path, | ||
| 295 | DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); | ||
| 296 | |||
| 297 | // Gets all of the text up to the last '/' or '\' in the path. | ||
| 298 | [[nodiscard]] std::string_view GetParentPath(std::string_view path); | ||
| 299 | |||
| 300 | // Gets all of the text after the first '/' or '\' in the path. | ||
| 301 | [[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); | ||
| 302 | |||
| 303 | // Gets the filename of the path | ||
| 304 | [[nodiscard]] std::string_view GetFilename(std::string_view path); | ||
| 305 | |||
| 306 | // Gets the extension of the filename | ||
| 307 | [[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); | ||
| 308 | |||
| 309 | } // namespace Common::FS | ||
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 96efa977d..6aa8ac960 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
| @@ -11,13 +11,13 @@ | |||
| 11 | #include <mutex> | 11 | #include <mutex> |
| 12 | #include <thread> | 12 | #include <thread> |
| 13 | #include <vector> | 13 | #include <vector> |
| 14 | |||
| 14 | #ifdef _WIN32 | 15 | #ifdef _WIN32 |
| 15 | #include <share.h> // For _SH_DENYWR | ||
| 16 | #include <windows.h> // For OutputDebugStringW | 16 | #include <windows.h> // For OutputDebugStringW |
| 17 | #else | ||
| 18 | #define _SH_DENYWR 0 | ||
| 19 | #endif | 17 | #endif |
| 18 | |||
| 20 | #include "common/assert.h" | 19 | #include "common/assert.h" |
| 20 | #include "common/fs/fs.h" | ||
| 21 | #include "common/logging/backend.h" | 21 | #include "common/logging/backend.h" |
| 22 | #include "common/logging/log.h" | 22 | #include "common/logging/log.h" |
| 23 | #include "common/logging/text_formatter.h" | 23 | #include "common/logging/text_formatter.h" |
| @@ -148,19 +148,16 @@ void ColorConsoleBackend::Write(const Entry& entry) { | |||
| 148 | PrintColoredMessage(entry); | 148 | PrintColoredMessage(entry); |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | FileBackend::FileBackend(const std::string& filename) { | 151 | FileBackend::FileBackend(const std::filesystem::path& filename) { |
| 152 | const auto old_filename = filename + ".old.txt"; | 152 | auto old_filename = filename; |
| 153 | old_filename += ".old.txt"; | ||
| 153 | 154 | ||
| 154 | if (FS::Exists(old_filename)) { | 155 | // Existence checks are done within the functions themselves. |
| 155 | FS::Delete(old_filename); | 156 | // We don't particularly care if these succeed or not. |
| 156 | } | 157 | void(FS::RemoveFile(old_filename)); |
| 157 | if (FS::Exists(filename)) { | 158 | void(FS::RenameFile(filename, old_filename)); |
| 158 | FS::Rename(filename, old_filename); | ||
| 159 | } | ||
| 160 | 159 | ||
| 161 | // _SH_DENYWR allows read only access to the file for other programs. | 160 | file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); |
| 162 | // It is #defined to 0 on other platforms | ||
| 163 | file = FS::IOFile(filename, "w", _SH_DENYWR); | ||
| 164 | } | 161 | } |
| 165 | 162 | ||
| 166 | void FileBackend::Write(const Entry& entry) { | 163 | void FileBackend::Write(const Entry& entry) { |
| @@ -181,7 +178,7 @@ void FileBackend::Write(const Entry& entry) { | |||
| 181 | 178 | ||
| 182 | bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); | 179 | bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); |
| 183 | if (entry.log_level >= Level::Error) { | 180 | if (entry.log_level >= Level::Error) { |
| 184 | file.Flush(); | 181 | void(file.Flush()); |
| 185 | } | 182 | } |
| 186 | } | 183 | } |
| 187 | 184 | ||
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 9dd2589c3..eb629a33f 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h | |||
| @@ -4,10 +4,11 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <chrono> | 6 | #include <chrono> |
| 7 | #include <filesystem> | ||
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <string> | 9 | #include <string> |
| 9 | #include <string_view> | 10 | #include <string_view> |
| 10 | #include "common/file_util.h" | 11 | #include "common/fs/file.h" |
| 11 | #include "common/logging/filter.h" | 12 | #include "common/logging/filter.h" |
| 12 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 13 | 14 | ||
| @@ -81,7 +82,7 @@ public: | |||
| 81 | */ | 82 | */ |
| 82 | class FileBackend : public Backend { | 83 | class FileBackend : public Backend { |
| 83 | public: | 84 | public: |
| 84 | explicit FileBackend(const std::string& filename); | 85 | explicit FileBackend(const std::filesystem::path& filename); |
| 85 | 86 | ||
| 86 | static const char* Name() { | 87 | static const char* Name() { |
| 87 | return "file"; | 88 | return "file"; |
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp index d537517db..d1afd1f1d 100644 --- a/src/common/nvidia_flags.cpp +++ b/src/common/nvidia_flags.cpp | |||
| @@ -2,24 +2,30 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <filesystem> | 5 | #include <cstdlib> |
| 6 | #include <stdlib.h> | ||
| 7 | 6 | ||
| 8 | #include <fmt/format.h> | 7 | #include <fmt/format.h> |
| 9 | 8 | ||
| 10 | #include "common/file_util.h" | 9 | #include "common/fs/file.h" |
| 10 | #include "common/fs/fs.h" | ||
| 11 | #include "common/fs/path_util.h" | ||
| 11 | #include "common/nvidia_flags.h" | 12 | #include "common/nvidia_flags.h" |
| 12 | 13 | ||
| 13 | namespace Common { | 14 | namespace Common { |
| 14 | 15 | ||
| 15 | void ConfigureNvidiaEnvironmentFlags() { | 16 | void ConfigureNvidiaEnvironmentFlags() { |
| 16 | #ifdef _WIN32 | 17 | #ifdef _WIN32 |
| 17 | const std::string shader_path = Common::FS::SanitizePath( | 18 | const auto nvidia_shader_dir = |
| 18 | fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir))); | 19 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "nvidia"; |
| 19 | const std::string windows_path = | 20 | |
| 20 | Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash); | 21 | if (!Common::FS::CreateDirs(nvidia_shader_dir)) { |
| 21 | void(Common::FS::CreateFullPath(shader_path + '/')); | 22 | return; |
| 22 | void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str())); | 23 | } |
| 24 | |||
| 25 | const auto windows_path_string = | ||
| 26 | Common::FS::PathToUTF8String(nvidia_shader_dir.lexically_normal()); | ||
| 27 | |||
| 28 | void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str())); | ||
| 23 | void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); | 29 | void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); |
| 24 | #endif | 30 | #endif |
| 25 | } | 31 | } |
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index e29cbf506..bcb4e4be1 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include <string_view> | 5 | #include <string_view> |
| 6 | 6 | ||
| 7 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 8 | #include "common/file_util.h" | 8 | #include "common/fs/path_util.h" |
| 9 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 10 | #include "common/settings.h" | 10 | #include "common/settings.h" |
| 11 | 11 | ||
| @@ -34,6 +34,10 @@ void LogSettings() { | |||
| 34 | LOG_INFO(Config, "{}: {}", name, value); | 34 | LOG_INFO(Config, "{}: {}", name, value); |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | const auto log_path = [](std::string_view name, const std::filesystem::path& path) { | ||
| 38 | LOG_INFO(Config, "{}: {}", name, Common::FS::PathToUTF8String(path)); | ||
| 39 | }; | ||
| 40 | |||
| 37 | LOG_INFO(Config, "yuzu Configuration:"); | 41 | LOG_INFO(Config, "yuzu Configuration:"); |
| 38 | log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); | 42 | log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); |
| 39 | log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); | 43 | log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); |
| @@ -59,11 +63,11 @@ void LogSettings() { | |||
| 59 | log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); | 63 | log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); |
| 60 | log_setting("Audio_OutputDevice", values.audio_device_id); | 64 | log_setting("Audio_OutputDevice", values.audio_device_id); |
| 61 | log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); | 65 | log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); |
| 62 | log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)); | 66 | log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); |
| 63 | log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); | 67 | log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); |
| 64 | log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)); | 68 | log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir)); |
| 65 | log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)); | 69 | log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); |
| 66 | log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)); | 70 | log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); |
| 67 | log_setting("Debugging_ProgramArgs", values.program_args); | 71 | log_setting("Debugging_ProgramArgs", values.program_args); |
| 68 | log_setting("Services_BCATBackend", values.bcat_backend); | 72 | log_setting("Services_BCATBackend", values.bcat_backend); |
| 69 | log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local); | 73 | log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local); |
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 7b614ad89..e6344fd41 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp | |||
| @@ -9,7 +9,6 @@ | |||
| 9 | #include <locale> | 9 | #include <locale> |
| 10 | #include <sstream> | 10 | #include <sstream> |
| 11 | 11 | ||
| 12 | #include "common/common_paths.h" | ||
| 13 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 14 | #include "common/string_util.h" | 13 | #include "common/string_util.h" |
| 15 | 14 | ||
| @@ -93,18 +92,6 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ | |||
| 93 | return true; | 92 | return true; |
| 94 | } | 93 | } |
| 95 | 94 | ||
| 96 | void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, | ||
| 97 | const std::string& _Filename) { | ||
| 98 | _CompleteFilename = _Path; | ||
| 99 | |||
| 100 | // check for seperator | ||
| 101 | if (DIR_SEP_CHR != *_CompleteFilename.rbegin()) | ||
| 102 | _CompleteFilename += DIR_SEP_CHR; | ||
| 103 | |||
| 104 | // add the filename | ||
| 105 | _CompleteFilename += _Filename; | ||
| 106 | } | ||
| 107 | |||
| 108 | void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) { | 95 | void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) { |
| 109 | std::istringstream iss(str); | 96 | std::istringstream iss(str); |
| 110 | output.resize(1); | 97 | output.resize(1); |
diff --git a/src/common/string_util.h b/src/common/string_util.h index a32c07c06..7e90a9ca5 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h | |||
| @@ -32,8 +32,6 @@ void SplitString(const std::string& str, char delim, std::vector<std::string>& o | |||
| 32 | bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, | 32 | bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, |
| 33 | std::string* _pExtension); | 33 | std::string* _pExtension); |
| 34 | 34 | ||
| 35 | void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, | ||
| 36 | const std::string& _Filename); | ||
| 37 | [[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, | 35 | [[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, |
| 38 | const std::string& dest); | 36 | const std::string& dest); |
| 39 | 37 | ||