diff options
Diffstat (limited to 'src')
131 files changed, 4482 insertions, 2918 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/lz4_compression.cpp b/src/common/lz4_compression.cpp index 25700015a..dbb40da7c 100644 --- a/src/common/lz4_compression.cpp +++ b/src/common/lz4_compression.cpp | |||
| @@ -59,8 +59,7 @@ std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size) | |||
| 59 | return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX); | 59 | return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, | 62 | std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t uncompressed_size) { |
| 63 | std::size_t uncompressed_size) { | ||
| 64 | std::vector<u8> uncompressed(uncompressed_size); | 63 | std::vector<u8> uncompressed(uncompressed_size); |
| 65 | const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()), | 64 | const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()), |
| 66 | reinterpret_cast<char*>(uncompressed.data()), | 65 | reinterpret_cast<char*>(uncompressed.data()), |
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h index 87a4be1b0..1b4717595 100644 --- a/src/common/lz4_compression.h +++ b/src/common/lz4_compression.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <span> | ||
| 7 | #include <vector> | 8 | #include <vector> |
| 8 | 9 | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| @@ -53,7 +54,7 @@ namespace Common::Compression { | |||
| 53 | * | 54 | * |
| 54 | * @return the decompressed data. | 55 | * @return the decompressed data. |
| 55 | */ | 56 | */ |
| 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, | 57 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, |
| 57 | std::size_t uncompressed_size); | 58 | std::size_t uncompressed_size); |
| 58 | 59 | ||
| 59 | } // namespace Common::Compression \ No newline at end of file | 60 | } // namespace Common::Compression |
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/parent_of_member.h b/src/common/parent_of_member.h index e0f8ab5c8..58c70b0e7 100644 --- a/src/common/parent_of_member.h +++ b/src/common/parent_of_member.h | |||
| @@ -109,7 +109,8 @@ struct OffsetOfCalculator { | |||
| 109 | } | 109 | } |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | return (next - start) * sizeof(MemberType) + Offset; | 112 | return static_cast<ptrdiff_t>(static_cast<size_t>(next - start) * sizeof(MemberType) + |
| 113 | Offset); | ||
| 113 | } | 114 | } |
| 114 | 115 | ||
| 115 | static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) { | 116 | static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) { |
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 702b6598d..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)); |
| @@ -42,7 +46,7 @@ void LogSettings() { | |||
| 42 | log_setting("System_RegionIndex", values.region_index.GetValue()); | 46 | log_setting("System_RegionIndex", values.region_index.GetValue()); |
| 43 | log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); | 47 | log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); |
| 44 | log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); | 48 | log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); |
| 45 | log_setting("CPU_Accuracy", values.cpu_accuracy); | 49 | log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue()); |
| 46 | log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue()); | 50 | log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue()); |
| 47 | log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue()); | 51 | log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue()); |
| 48 | log_setting("Renderer_FrameLimit", values.frame_limit.GetValue()); | 52 | log_setting("Renderer_FrameLimit", values.frame_limit.GetValue()); |
| @@ -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); |
| @@ -106,6 +110,12 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 106 | // Core | 110 | // Core |
| 107 | values.use_multi_core.SetGlobal(true); | 111 | values.use_multi_core.SetGlobal(true); |
| 108 | 112 | ||
| 113 | // CPU | ||
| 114 | values.cpu_accuracy.SetGlobal(true); | ||
| 115 | values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); | ||
| 116 | values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); | ||
| 117 | values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); | ||
| 118 | |||
| 109 | // Renderer | 119 | // Renderer |
| 110 | values.renderer_backend.SetGlobal(true); | 120 | values.renderer_backend.SetGlobal(true); |
| 111 | values.vulkan_device.SetGlobal(true); | 121 | values.vulkan_device.SetGlobal(true); |
| @@ -130,7 +140,6 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 130 | values.region_index.SetGlobal(true); | 140 | values.region_index.SetGlobal(true); |
| 131 | values.time_zone_index.SetGlobal(true); | 141 | values.time_zone_index.SetGlobal(true); |
| 132 | values.rng_seed.SetGlobal(true); | 142 | values.rng_seed.SetGlobal(true); |
| 133 | values.custom_rtc.SetGlobal(true); | ||
| 134 | values.sound_index.SetGlobal(true); | 143 | values.sound_index.SetGlobal(true); |
| 135 | 144 | ||
| 136 | // Controls | 145 | // Controls |
diff --git a/src/common/settings.h b/src/common/settings.h index d39b4aa45..48085b9a9 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -115,7 +115,7 @@ struct Values { | |||
| 115 | Setting<bool> use_multi_core; | 115 | Setting<bool> use_multi_core; |
| 116 | 116 | ||
| 117 | // Cpu | 117 | // Cpu |
| 118 | CPUAccuracy cpu_accuracy; | 118 | Setting<CPUAccuracy> cpu_accuracy; |
| 119 | 119 | ||
| 120 | bool cpuopt_page_tables; | 120 | bool cpuopt_page_tables; |
| 121 | bool cpuopt_block_linking; | 121 | bool cpuopt_block_linking; |
| @@ -126,9 +126,9 @@ struct Values { | |||
| 126 | bool cpuopt_misc_ir; | 126 | bool cpuopt_misc_ir; |
| 127 | bool cpuopt_reduce_misalign_checks; | 127 | bool cpuopt_reduce_misalign_checks; |
| 128 | 128 | ||
| 129 | bool cpuopt_unsafe_unfuse_fma; | 129 | Setting<bool> cpuopt_unsafe_unfuse_fma; |
| 130 | bool cpuopt_unsafe_reduce_fp_error; | 130 | Setting<bool> cpuopt_unsafe_reduce_fp_error; |
| 131 | bool cpuopt_unsafe_inaccurate_nan; | 131 | Setting<bool> cpuopt_unsafe_inaccurate_nan; |
| 132 | 132 | ||
| 133 | // Renderer | 133 | // Renderer |
| 134 | Setting<RendererBackend> renderer_backend; | 134 | Setting<RendererBackend> renderer_backend; |
| @@ -157,7 +157,7 @@ struct Values { | |||
| 157 | // System | 157 | // System |
| 158 | Setting<std::optional<u32>> rng_seed; | 158 | Setting<std::optional<u32>> rng_seed; |
| 159 | // Measured in seconds since epoch | 159 | // Measured in seconds since epoch |
| 160 | Setting<std::optional<std::chrono::seconds>> custom_rtc; | 160 | std::optional<std::chrono::seconds> custom_rtc; |
| 161 | // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` | 161 | // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` |
| 162 | std::chrono::seconds custom_rtc_differential; | 162 | std::chrono::seconds custom_rtc_differential; |
| 163 | 163 | ||
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 | ||
diff --git a/src/common/tree.h b/src/common/tree.h index 9d2d0df4e..18faa4a48 100644 --- a/src/common/tree.h +++ b/src/common/tree.h | |||
| @@ -43,6 +43,8 @@ | |||
| 43 | * The maximum height of a red-black tree is 2lg (n+1). | 43 | * The maximum height of a red-black tree is 2lg (n+1). |
| 44 | */ | 44 | */ |
| 45 | 45 | ||
| 46 | #include "common/assert.h" | ||
| 47 | |||
| 46 | namespace Common { | 48 | namespace Common { |
| 47 | template <typename T> | 49 | template <typename T> |
| 48 | class RBHead { | 50 | class RBHead { |
| @@ -325,6 +327,10 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) { | |||
| 325 | while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root() && parent != nullptr) { | 327 | while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root() && parent != nullptr) { |
| 326 | if (RB_LEFT(parent) == elm) { | 328 | if (RB_LEFT(parent) == elm) { |
| 327 | tmp = RB_RIGHT(parent); | 329 | tmp = RB_RIGHT(parent); |
| 330 | if (!tmp) { | ||
| 331 | ASSERT_MSG(false, "tmp is invalid!"); | ||
| 332 | break; | ||
| 333 | } | ||
| 328 | if (RB_IS_RED(tmp)) { | 334 | if (RB_IS_RED(tmp)) { |
| 329 | RB_SET_BLACKRED(tmp, parent); | 335 | RB_SET_BLACKRED(tmp, parent); |
| 330 | RB_ROTATE_LEFT(head, parent, tmp); | 336 | RB_ROTATE_LEFT(head, parent, tmp); |
| @@ -366,6 +372,11 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) { | |||
| 366 | tmp = RB_LEFT(parent); | 372 | tmp = RB_LEFT(parent); |
| 367 | } | 373 | } |
| 368 | 374 | ||
| 375 | if (!tmp) { | ||
| 376 | ASSERT_MSG(false, "tmp is invalid!"); | ||
| 377 | break; | ||
| 378 | } | ||
| 379 | |||
| 369 | if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && | 380 | if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && |
| 370 | (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { | 381 | (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { |
| 371 | RB_SET_COLOR(tmp, EntryColor::Red); | 382 | RB_SET_COLOR(tmp, EntryColor::Red); |
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp index 5f45459da..695b96a43 100644 --- a/src/common/zstd_compression.cpp +++ b/src/common/zstd_compression.cpp | |||
| @@ -32,7 +32,7 @@ std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_siz | |||
| 32 | return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT); | 32 | return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT); |
| 33 | } | 33 | } |
| 34 | 34 | ||
| 35 | std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) { | 35 | std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) { |
| 36 | const std::size_t decompressed_size = | 36 | const std::size_t decompressed_size = |
| 37 | ZSTD_getDecompressedSize(compressed.data(), compressed.size()); | 37 | ZSTD_getDecompressedSize(compressed.data(), compressed.size()); |
| 38 | std::vector<u8> decompressed(decompressed_size); | 38 | std::vector<u8> decompressed(decompressed_size); |
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h index c26a30ab9..bbce14f4e 100644 --- a/src/common/zstd_compression.h +++ b/src/common/zstd_compression.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <span> | ||
| 7 | #include <vector> | 8 | #include <vector> |
| 8 | 9 | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| @@ -40,6 +41,6 @@ namespace Common::Compression { | |||
| 40 | * | 41 | * |
| 41 | * @return the decompressed data. | 42 | * @return the decompressed data. |
| 42 | */ | 43 | */ |
| 43 | [[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed); | 44 | [[nodiscard]] std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed); |
| 44 | 45 | ||
| 45 | } // namespace Common::Compression \ No newline at end of file | 46 | } // namespace Common::Compression |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index ab3266916..93d43e22e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp | |||
| @@ -142,7 +142,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* | |||
| 142 | config.far_code_offset = 256 * 1024 * 1024; | 142 | config.far_code_offset = 256 * 1024 * 1024; |
| 143 | 143 | ||
| 144 | // Safe optimizations | 144 | // Safe optimizations |
| 145 | if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { | 145 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { |
| 146 | if (!Settings::values.cpuopt_page_tables) { | 146 | if (!Settings::values.cpuopt_page_tables) { |
| 147 | config.page_table = nullptr; | 147 | config.page_table = nullptr; |
| 148 | } | 148 | } |
| @@ -170,15 +170,15 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* | |||
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | // Unsafe optimizations | 172 | // Unsafe optimizations |
| 173 | if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { | 173 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { |
| 174 | config.unsafe_optimizations = true; | 174 | config.unsafe_optimizations = true; |
| 175 | if (Settings::values.cpuopt_unsafe_unfuse_fma) { | 175 | if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) { |
| 176 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; | 176 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; |
| 177 | } | 177 | } |
| 178 | if (Settings::values.cpuopt_unsafe_reduce_fp_error) { | 178 | if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { |
| 179 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; | 179 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; |
| 180 | } | 180 | } |
| 181 | if (Settings::values.cpuopt_unsafe_inaccurate_nan) { | 181 | if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { |
| 182 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; | 182 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; |
| 183 | } | 183 | } |
| 184 | } | 184 | } |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index a4d830e48..08fa85904 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp | |||
| @@ -182,7 +182,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 182 | config.far_code_offset = 256 * 1024 * 1024; | 182 | config.far_code_offset = 256 * 1024 * 1024; |
| 183 | 183 | ||
| 184 | // Safe optimizations | 184 | // Safe optimizations |
| 185 | if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { | 185 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { |
| 186 | if (!Settings::values.cpuopt_page_tables) { | 186 | if (!Settings::values.cpuopt_page_tables) { |
| 187 | config.page_table = nullptr; | 187 | config.page_table = nullptr; |
| 188 | } | 188 | } |
| @@ -210,15 +210,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | // Unsafe optimizations | 212 | // Unsafe optimizations |
| 213 | if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { | 213 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { |
| 214 | config.unsafe_optimizations = true; | 214 | config.unsafe_optimizations = true; |
| 215 | if (Settings::values.cpuopt_unsafe_unfuse_fma) { | 215 | if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) { |
| 216 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; | 216 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; |
| 217 | } | 217 | } |
| 218 | if (Settings::values.cpuopt_unsafe_reduce_fp_error) { | 218 | if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { |
| 219 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; | 219 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; |
| 220 | } | 220 | } |
| 221 | if (Settings::values.cpuopt_unsafe_inaccurate_nan) { | 221 | if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { |
| 222 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; | 222 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; |
| 223 | } | 223 | } |
| 224 | } | 224 | } |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 434bf3262..c5004b7b4 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <utility> | 7 | #include <utility> |
| 8 | 8 | ||
| 9 | #include "common/file_util.h" | 9 | #include "common/fs/fs.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/microprofile.h" | 11 | #include "common/microprofile.h" |
| 12 | #include "common/settings.h" | 12 | #include "common/settings.h" |
| @@ -121,7 +121,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, | |||
| 121 | dir->GetName()); | 121 | dir->GetName()); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | if (Common::FS::IsDirectory(path)) { | 124 | if (Common::FS::IsDir(path)) { |
| 125 | return vfs->OpenFile(path + "/main", FileSys::Mode::Read); | 125 | return vfs->OpenFile(path + "/main", FileSys::Mode::Read); |
| 126 | } | 126 | } |
| 127 | 127 | ||
| @@ -173,7 +173,7 @@ struct System::Impl { | |||
| 173 | const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( | 173 | const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( |
| 174 | std::chrono::system_clock::now().time_since_epoch()); | 174 | std::chrono::system_clock::now().time_since_epoch()); |
| 175 | Settings::values.custom_rtc_differential = | 175 | Settings::values.custom_rtc_differential = |
| 176 | Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time; | 176 | Settings::values.custom_rtc.value_or(current_time) - current_time; |
| 177 | 177 | ||
| 178 | // Create a default fs if one doesn't already exist. | 178 | // Create a default fs if one doesn't already exist. |
| 179 | if (virtual_filesystem == nullptr) | 179 | if (virtual_filesystem == nullptr) |
| @@ -289,7 +289,8 @@ struct System::Impl { | |||
| 289 | 289 | ||
| 290 | telemetry_session->AddField(performance, "Shutdown_EmulationSpeed", | 290 | telemetry_session->AddField(performance, "Shutdown_EmulationSpeed", |
| 291 | perf_results.emulation_speed * 100.0); | 291 | perf_results.emulation_speed * 100.0); |
| 292 | telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps); | 292 | telemetry_session->AddField(performance, "Shutdown_Framerate", |
| 293 | perf_results.average_game_fps); | ||
| 293 | telemetry_session->AddField(performance, "Shutdown_Frametime", | 294 | telemetry_session->AddField(performance, "Shutdown_Frametime", |
| 294 | perf_results.frametime * 1000.0); | 295 | perf_results.frametime * 1000.0); |
| 295 | telemetry_session->AddField(performance, "Mean_Frametime_MS", | 296 | telemetry_session->AddField(performance, "Mean_Frametime_MS", |
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index a4b739c63..fb451a423 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -18,8 +18,9 @@ | |||
| 18 | #include <mbedtls/cmac.h> | 18 | #include <mbedtls/cmac.h> |
| 19 | #include <mbedtls/sha256.h> | 19 | #include <mbedtls/sha256.h> |
| 20 | #include "common/common_funcs.h" | 20 | #include "common/common_funcs.h" |
| 21 | #include "common/common_paths.h" | 21 | #include "common/fs/file.h" |
| 22 | #include "common/file_util.h" | 22 | #include "common/fs/fs.h" |
| 23 | #include "common/fs/path_util.h" | ||
| 23 | #include "common/hex_util.h" | 24 | #include "common/hex_util.h" |
| 24 | #include "common/logging/log.h" | 25 | #include "common/logging/log.h" |
| 25 | #include "common/settings.h" | 26 | #include "common/settings.h" |
| @@ -325,46 +326,55 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) | |||
| 325 | } | 326 | } |
| 326 | 327 | ||
| 327 | std::optional<Key128> DeriveSDSeed() { | 328 | std::optional<Key128> DeriveSDSeed() { |
| 328 | const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 329 | const auto system_save_43_path = |
| 329 | "/system/save/8000000000000043", | 330 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000043"; |
| 330 | "rb+"); | 331 | const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read, |
| 332 | Common::FS::FileType::BinaryFile}; | ||
| 333 | |||
| 331 | if (!save_43.IsOpen()) { | 334 | if (!save_43.IsOpen()) { |
| 332 | return std::nullopt; | 335 | return std::nullopt; |
| 333 | } | 336 | } |
| 334 | 337 | ||
| 335 | const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + | 338 | const auto sd_private_path = |
| 336 | "/Nintendo/Contents/private", | 339 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/private"; |
| 337 | "rb+"); | 340 | |
| 341 | const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read, | ||
| 342 | Common::FS::FileType::BinaryFile}; | ||
| 343 | |||
| 338 | if (!sd_private.IsOpen()) { | 344 | if (!sd_private.IsOpen()) { |
| 339 | return std::nullopt; | 345 | return std::nullopt; |
| 340 | } | 346 | } |
| 341 | 347 | ||
| 342 | std::array<u8, 0x10> private_seed{}; | 348 | std::array<u8, 0x10> private_seed{}; |
| 343 | if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { | 349 | if (sd_private.Read(private_seed) != private_seed.size()) { |
| 344 | return std::nullopt; | 350 | return std::nullopt; |
| 345 | } | 351 | } |
| 346 | 352 | ||
| 347 | std::array<u8, 0x10> buffer{}; | 353 | std::array<u8, 0x10> buffer{}; |
| 348 | std::size_t offset = 0; | 354 | s64 offset = 0; |
| 349 | for (; offset + 0x10 < save_43.GetSize(); ++offset) { | 355 | for (; offset + 0x10 < static_cast<s64>(save_43.GetSize()); ++offset) { |
| 350 | if (!save_43.Seek(offset, SEEK_SET)) { | 356 | if (!save_43.Seek(offset)) { |
| 357 | return std::nullopt; | ||
| 358 | } | ||
| 359 | |||
| 360 | if (save_43.Read(buffer) != buffer.size()) { | ||
| 351 | return std::nullopt; | 361 | return std::nullopt; |
| 352 | } | 362 | } |
| 353 | 363 | ||
| 354 | save_43.ReadBytes(buffer.data(), buffer.size()); | ||
| 355 | if (buffer == private_seed) { | 364 | if (buffer == private_seed) { |
| 356 | break; | 365 | break; |
| 357 | } | 366 | } |
| 358 | } | 367 | } |
| 359 | 368 | ||
| 360 | if (!save_43.Seek(offset + 0x10, SEEK_SET)) { | 369 | if (!save_43.Seek(offset + 0x10)) { |
| 361 | return std::nullopt; | 370 | return std::nullopt; |
| 362 | } | 371 | } |
| 363 | 372 | ||
| 364 | Key128 seed{}; | 373 | Key128 seed{}; |
| 365 | if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { | 374 | if (save_43.Read(seed) != seed.size()) { |
| 366 | return std::nullopt; | 375 | return std::nullopt; |
| 367 | } | 376 | } |
| 377 | |||
| 368 | return seed; | 378 | return seed; |
| 369 | } | 379 | } |
| 370 | 380 | ||
| @@ -435,7 +445,7 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { | |||
| 435 | } | 445 | } |
| 436 | 446 | ||
| 437 | std::vector<u8> buffer(ticket_save.GetSize()); | 447 | std::vector<u8> buffer(ticket_save.GetSize()); |
| 438 | if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { | 448 | if (ticket_save.Read(buffer) != buffer.size()) { |
| 439 | return {}; | 449 | return {}; |
| 440 | } | 450 | } |
| 441 | 451 | ||
| @@ -566,27 +576,26 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 566 | 576 | ||
| 567 | KeyManager::KeyManager() { | 577 | KeyManager::KeyManager() { |
| 568 | // Initialize keys | 578 | // Initialize keys |
| 569 | const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); | 579 | const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); |
| 570 | const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); | ||
| 571 | 580 | ||
| 572 | if (!Common::FS::Exists(yuzu_keys_dir)) { | 581 | if (!Common::FS::CreateDir(yuzu_keys_dir)) { |
| 573 | Common::FS::CreateDir(yuzu_keys_dir); | 582 | LOG_ERROR(Core, "Failed to create the keys directory."); |
| 574 | } | 583 | } |
| 575 | 584 | ||
| 576 | if (Settings::values.use_dev_keys) { | 585 | if (Settings::values.use_dev_keys) { |
| 577 | dev_mode = true; | 586 | dev_mode = true; |
| 578 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); | 587 | LoadFromFile(yuzu_keys_dir / "dev.keys", false); |
| 579 | AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); | 588 | LoadFromFile(yuzu_keys_dir / "dev.keys_autogenerated", false); |
| 580 | } else { | 589 | } else { |
| 581 | dev_mode = false; | 590 | dev_mode = false; |
| 582 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); | 591 | LoadFromFile(yuzu_keys_dir / "prod.keys", false); |
| 583 | AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); | 592 | LoadFromFile(yuzu_keys_dir / "prod.keys_autogenerated", false); |
| 584 | } | 593 | } |
| 585 | 594 | ||
| 586 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); | 595 | LoadFromFile(yuzu_keys_dir / "title.keys", true); |
| 587 | AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); | 596 | LoadFromFile(yuzu_keys_dir / "title.keys_autogenerated", true); |
| 588 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); | 597 | LoadFromFile(yuzu_keys_dir / "console.keys", false); |
| 589 | AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); | 598 | LoadFromFile(yuzu_keys_dir / "console.keys_autogenerated", false); |
| 590 | } | 599 | } |
| 591 | 600 | ||
| 592 | static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { | 601 | static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { |
| @@ -597,9 +606,14 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_ | |||
| 597 | [](u8 c) { return std::isxdigit(c); }); | 606 | [](u8 c) { return std::isxdigit(c); }); |
| 598 | } | 607 | } |
| 599 | 608 | ||
| 600 | void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { | 609 | void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) { |
| 610 | if (!Common::FS::Exists(file_path)) { | ||
| 611 | return; | ||
| 612 | } | ||
| 613 | |||
| 601 | std::ifstream file; | 614 | std::ifstream file; |
| 602 | Common::FS::OpenFStream(file, filename, std::ios_base::in); | 615 | Common::FS::OpenFileStream(file, file_path, std::ios_base::in); |
| 616 | |||
| 603 | if (!file.is_open()) { | 617 | if (!file.is_open()) { |
| 604 | return; | 618 | return; |
| 605 | } | 619 | } |
| @@ -694,15 +708,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { | |||
| 694 | } | 708 | } |
| 695 | } | 709 | } |
| 696 | 710 | ||
| 697 | void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, | ||
| 698 | const std::string& filename, bool title) { | ||
| 699 | if (Common::FS::Exists(dir1 + DIR_SEP + filename)) { | ||
| 700 | LoadFromFile(dir1 + DIR_SEP + filename, title); | ||
| 701 | } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) { | ||
| 702 | LoadFromFile(dir2 + DIR_SEP + filename, title); | ||
| 703 | } | ||
| 704 | } | ||
| 705 | |||
| 706 | bool KeyManager::BaseDeriveNecessary() const { | 711 | bool KeyManager::BaseDeriveNecessary() const { |
| 707 | const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { | 712 | const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { |
| 708 | return !HasKey(key_type, index1, index2); | 713 | return !HasKey(key_type, index1, index2); |
| @@ -766,30 +771,35 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { | |||
| 766 | template <size_t Size> | 771 | template <size_t Size> |
| 767 | void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, | 772 | void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, |
| 768 | const std::array<u8, Size>& key) { | 773 | const std::array<u8, Size>& key) { |
| 769 | const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); | 774 | const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); |
| 775 | |||
| 770 | std::string filename = "title.keys_autogenerated"; | 776 | std::string filename = "title.keys_autogenerated"; |
| 777 | |||
| 771 | if (category == KeyCategory::Standard) { | 778 | if (category == KeyCategory::Standard) { |
| 772 | filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; | 779 | filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; |
| 773 | } else if (category == KeyCategory::Console) { | 780 | } else if (category == KeyCategory::Console) { |
| 774 | filename = "console.keys_autogenerated"; | 781 | filename = "console.keys_autogenerated"; |
| 775 | } | 782 | } |
| 776 | 783 | ||
| 777 | const auto path = yuzu_keys_dir + DIR_SEP + filename; | 784 | const auto path = yuzu_keys_dir / filename; |
| 778 | const auto add_info_text = !Common::FS::Exists(path); | 785 | const auto add_info_text = !Common::FS::Exists(path); |
| 779 | Common::FS::CreateFullPath(path); | 786 | |
| 780 | Common::FS::IOFile file{path, "a"}; | 787 | Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append, |
| 788 | Common::FS::FileType::TextFile}; | ||
| 789 | |||
| 781 | if (!file.IsOpen()) { | 790 | if (!file.IsOpen()) { |
| 782 | return; | 791 | return; |
| 783 | } | 792 | } |
| 793 | |||
| 784 | if (add_info_text) { | 794 | if (add_info_text) { |
| 785 | file.WriteString( | 795 | void(file.WriteString( |
| 786 | "# This file is autogenerated by Yuzu\n" | 796 | "# This file is autogenerated by Yuzu\n" |
| 787 | "# It serves to store keys that were automatically generated from the normal keys\n" | 797 | "# It serves to store keys that were automatically generated from the normal keys\n" |
| 788 | "# If you are experiencing issues involving keys, it may help to delete this file\n"); | 798 | "# If you are experiencing issues involving keys, it may help to delete this file\n")); |
| 789 | } | 799 | } |
| 790 | 800 | ||
| 791 | file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))); | 801 | void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key)))); |
| 792 | AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); | 802 | LoadFromFile(path, category == KeyCategory::Title); |
| 793 | } | 803 | } |
| 794 | 804 | ||
| 795 | void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { | 805 | void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { |
| @@ -861,20 +871,17 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { | |||
| 861 | } | 871 | } |
| 862 | 872 | ||
| 863 | bool KeyManager::KeyFileExists(bool title) { | 873 | bool KeyManager::KeyFileExists(bool title) { |
| 864 | const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); | 874 | const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); |
| 865 | const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); | 875 | |
| 866 | if (title) { | 876 | if (title) { |
| 867 | return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || | 877 | return Common::FS::Exists(yuzu_keys_dir / "title.keys"); |
| 868 | Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); | ||
| 869 | } | 878 | } |
| 870 | 879 | ||
| 871 | if (Settings::values.use_dev_keys) { | 880 | if (Settings::values.use_dev_keys) { |
| 872 | return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || | 881 | return Common::FS::Exists(yuzu_keys_dir / "dev.keys"); |
| 873 | Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); | ||
| 874 | } | 882 | } |
| 875 | 883 | ||
| 876 | return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || | 884 | return Common::FS::Exists(yuzu_keys_dir / "prod.keys"); |
| 877 | Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); | ||
| 878 | } | 885 | } |
| 879 | 886 | ||
| 880 | void KeyManager::DeriveSDSeedLazy() { | 887 | void KeyManager::DeriveSDSeedLazy() { |
| @@ -1115,15 +1122,21 @@ void KeyManager::PopulateTickets() { | |||
| 1115 | return; | 1122 | return; |
| 1116 | } | 1123 | } |
| 1117 | 1124 | ||
| 1118 | const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 1125 | const auto system_save_e1_path = |
| 1119 | "/system/save/80000000000000e1", | 1126 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; |
| 1120 | "rb+"); | 1127 | |
| 1121 | const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 1128 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, |
| 1122 | "/system/save/80000000000000e2", | 1129 | Common::FS::FileType::BinaryFile}; |
| 1123 | "rb+"); | 1130 | |
| 1131 | const auto system_save_e2_path = | ||
| 1132 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; | ||
| 1133 | |||
| 1134 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | ||
| 1135 | Common::FS::FileType::BinaryFile}; | ||
| 1136 | |||
| 1137 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1138 | auto res = GetTicketblob(save_e1); | ||
| 1124 | 1139 | ||
| 1125 | const auto blob2 = GetTicketblob(save2); | ||
| 1126 | auto res = GetTicketblob(save1); | ||
| 1127 | const auto idx = res.size(); | 1140 | const auto idx = res.size(); |
| 1128 | res.insert(res.end(), blob2.begin(), blob2.end()); | 1141 | res.insert(res.end(), blob2.begin(), blob2.end()); |
| 1129 | 1142 | ||
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 0a7220286..e771625e1 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <filesystem> | ||
| 8 | #include <map> | 9 | #include <map> |
| 9 | #include <optional> | 10 | #include <optional> |
| 10 | #include <string> | 11 | #include <string> |
| @@ -283,9 +284,8 @@ private: | |||
| 283 | std::array<u8, 576> eticket_extended_kek{}; | 284 | std::array<u8, 576> eticket_extended_kek{}; |
| 284 | 285 | ||
| 285 | bool dev_mode; | 286 | bool dev_mode; |
| 286 | void LoadFromFile(const std::string& filename, bool is_title_keys); | 287 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); |
| 287 | void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, | 288 | |
| 288 | const std::string& filename, bool title); | ||
| 289 | template <size_t Size> | 289 | template <size_t Size> |
| 290 | void WriteKeyToFile(KeyCategory category, std::string_view keyname, | 290 | void WriteKeyToFile(KeyCategory category, std::string_view keyname, |
| 291 | const std::array<u8, Size>& key); | 291 | const std::array<u8, Size>& key); |
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 7c6304ff0..f3891acf1 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <fmt/format.h> | 5 | #include <fmt/format.h> |
| 6 | #include "common/file_util.h" | 6 | #include "common/fs/path_util.h" |
| 7 | #include "core/file_sys/bis_factory.h" | 7 | #include "core/file_sys/bis_factory.h" |
| 8 | #include "core/file_sys/mode.h" | 8 | #include "core/file_sys/mode.h" |
| 9 | #include "core/file_sys/registered_cache.h" | 9 | #include "core/file_sys/registered_cache.h" |
| @@ -85,7 +85,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id, | |||
| 85 | VirtualFilesystem file_system) const { | 85 | VirtualFilesystem file_system) const { |
| 86 | auto& keys = Core::Crypto::KeyManager::Instance(); | 86 | auto& keys = Core::Crypto::KeyManager::Instance(); |
| 87 | Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( | 87 | Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( |
| 88 | Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)}; | 88 | Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)}; |
| 89 | keys.PopulateFromPartitionData(pdm); | 89 | keys.PopulateFromPartitionData(pdm); |
| 90 | 90 | ||
| 91 | switch (id) { | 91 | switch (id) { |
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h index 2b4f21073..6c49a64e2 100644 --- a/src/core/file_sys/mode.h +++ b/src/core/file_sys/mode.h | |||
| @@ -10,11 +10,13 @@ | |||
| 10 | namespace FileSys { | 10 | namespace FileSys { |
| 11 | 11 | ||
| 12 | enum class Mode : u32 { | 12 | enum class Mode : u32 { |
| 13 | Read = 1, | 13 | Read = 1 << 0, |
| 14 | Write = 2, | 14 | Write = 1 << 1, |
| 15 | ReadWrite = Read | Write, | 15 | ReadWrite = Read | Write, |
| 16 | Append = 4, | 16 | Append = 1 << 2, |
| 17 | ReadAppend = Read | Append, | ||
| 17 | WriteAppend = Write | Append, | 18 | WriteAppend = Write | Append, |
| 19 | All = ReadWrite | Append, | ||
| 18 | }; | 20 | }; |
| 19 | 21 | ||
| 20 | DECLARE_ENUM_FLAG_OPERATORS(Mode) | 22 | DECLARE_ENUM_FLAG_OPERATORS(Mode) |
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 48a2ed4d4..c5967049e 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | #include <iterator> | 8 | #include <iterator> |
| 9 | #include <utility> | 9 | #include <utility> |
| 10 | 10 | ||
| 11 | #include "common/file_util.h" | ||
| 12 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 13 | #include "core/file_sys/partition_filesystem.h" | 12 | #include "core/file_sys/partition_filesystem.h" |
| 14 | #include "core/file_sys/vfs_offset.h" | 13 | #include "core/file_sys/vfs_offset.h" |
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index cc9b4b637..53b8b7ca0 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -7,7 +7,6 @@ | |||
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <cstring> | 8 | #include <cstring> |
| 9 | 9 | ||
| 10 | #include "common/file_util.h" | ||
| 11 | #include "common/hex_util.h" | 10 | #include "common/hex_util.h" |
| 12 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 13 | #include "common/settings.h" | 12 | #include "common/settings.h" |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index b0cb65952..066c6789a 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include <regex> | 7 | #include <regex> |
| 8 | #include <mbedtls/sha256.h> | 8 | #include <mbedtls/sha256.h> |
| 9 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 10 | #include "common/file_util.h" | 10 | #include "common/fs/path_util.h" |
| 11 | #include "common/hex_util.h" | 11 | #include "common/hex_util.h" |
| 12 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 13 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index f497e9396..215e1cb1a 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp | |||
| @@ -5,8 +5,7 @@ | |||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <numeric> | 6 | #include <numeric> |
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include "common/common_paths.h" | 8 | #include "common/fs/path_util.h" |
| 9 | #include "common/file_util.h" | ||
| 10 | #include "common/logging/backend.h" | 9 | #include "common/logging/backend.h" |
| 11 | #include "core/file_sys/mode.h" | 10 | #include "core/file_sys/mode.h" |
| 12 | #include "core/file_sys/vfs.h" | 11 | #include "core/file_sys/vfs.h" |
| @@ -122,15 +121,14 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_ | |||
| 122 | return nullptr; | 121 | return nullptr; |
| 123 | 122 | ||
| 124 | for (const auto& file : old_dir->GetFiles()) { | 123 | for (const auto& file : old_dir->GetFiles()) { |
| 125 | const auto x = | 124 | const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName()); |
| 126 | CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName()); | ||
| 127 | if (x == nullptr) | 125 | if (x == nullptr) |
| 128 | return nullptr; | 126 | return nullptr; |
| 129 | } | 127 | } |
| 130 | 128 | ||
| 131 | for (const auto& dir : old_dir->GetSubdirectories()) { | 129 | for (const auto& dir : old_dir->GetSubdirectories()) { |
| 132 | const auto x = | 130 | const auto x = |
| 133 | CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName()); | 131 | CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName()); |
| 134 | if (x == nullptr) | 132 | if (x == nullptr) |
| 135 | return nullptr; | 133 | return nullptr; |
| 136 | } | 134 | } |
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp index 618eb658a..cd162c0c3 100644 --- a/src/core/file_sys/vfs_libzip.cpp +++ b/src/core/file_sys/vfs_libzip.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #pragma GCC diagnostic pop | 13 | #pragma GCC diagnostic pop |
| 14 | #endif | 14 | #endif |
| 15 | 15 | ||
| 16 | #include "common/fs/path_util.h" | ||
| 16 | #include "common/logging/backend.h" | 17 | #include "common/logging/backend.h" |
| 17 | #include "core/file_sys/vfs.h" | 18 | #include "core/file_sys/vfs.h" |
| 18 | #include "core/file_sys/vfs_libzip.h" | 19 | #include "core/file_sys/vfs_libzip.h" |
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 3d89dd644..d0b8fd046 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp | |||
| @@ -7,8 +7,9 @@ | |||
| 7 | #include <iterator> | 7 | #include <iterator> |
| 8 | #include <utility> | 8 | #include <utility> |
| 9 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 10 | #include "common/common_paths.h" | 10 | #include "common/fs/file.h" |
| 11 | #include "common/file_util.h" | 11 | #include "common/fs/fs.h" |
| 12 | #include "common/fs/path_util.h" | ||
| 12 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 13 | #include "core/file_sys/vfs_real.h" | 14 | #include "core/file_sys/vfs_real.h" |
| 14 | 15 | ||
| @@ -16,33 +17,31 @@ namespace FileSys { | |||
| 16 | 17 | ||
| 17 | namespace FS = Common::FS; | 18 | namespace FS = Common::FS; |
| 18 | 19 | ||
| 19 | static std::string ModeFlagsToString(Mode mode) { | 20 | namespace { |
| 20 | std::string mode_str; | 21 | |
| 21 | 22 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { | |
| 22 | // Calculate the correct open mode for the file. | 23 | switch (mode) { |
| 23 | if (True(mode & Mode::Read) && True(mode & Mode::Write)) { | 24 | case Mode::Read: |
| 24 | if (True(mode & Mode::Append)) { | 25 | return FS::FileAccessMode::Read; |
| 25 | mode_str = "a+"; | 26 | case Mode::Write: |
| 26 | } else { | 27 | return FS::FileAccessMode::Write; |
| 27 | mode_str = "r+"; | 28 | case Mode::ReadWrite: |
| 28 | } | 29 | return FS::FileAccessMode::ReadWrite; |
| 29 | } else { | 30 | case Mode::Append: |
| 30 | if (True(mode & Mode::Read)) { | 31 | return FS::FileAccessMode::Append; |
| 31 | mode_str = "r"; | 32 | case Mode::ReadAppend: |
| 32 | } else if (True(mode & Mode::Append)) { | 33 | return FS::FileAccessMode::ReadAppend; |
| 33 | mode_str = "a"; | 34 | case Mode::WriteAppend: |
| 34 | } else if (True(mode & Mode::Write)) { | 35 | return FS::FileAccessMode::Append; |
| 35 | mode_str = "w"; | 36 | case Mode::All: |
| 36 | } else { | 37 | return FS::FileAccessMode::ReadAppend; |
| 37 | UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode)); | 38 | default: |
| 38 | } | 39 | return {}; |
| 39 | } | 40 | } |
| 40 | |||
| 41 | mode_str += "b"; | ||
| 42 | |||
| 43 | return mode_str; | ||
| 44 | } | 41 | } |
| 45 | 42 | ||
| 43 | } // Anonymous namespace | ||
| 44 | |||
| 46 | RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} | 45 | RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} |
| 47 | RealVfsFilesystem::~RealVfsFilesystem() = default; | 46 | RealVfsFilesystem::~RealVfsFilesystem() = default; |
| 48 | 47 | ||
| @@ -63,7 +62,7 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { | |||
| 63 | if (!FS::Exists(path)) { | 62 | if (!FS::Exists(path)) { |
| 64 | return VfsEntryType::None; | 63 | return VfsEntryType::None; |
| 65 | } | 64 | } |
| 66 | if (FS::IsDirectory(path)) { | 65 | if (FS::IsDir(path)) { |
| 67 | return VfsEntryType::Directory; | 66 | return VfsEntryType::Directory; |
| 68 | } | 67 | } |
| 69 | 68 | ||
| @@ -81,12 +80,13 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | |||
| 81 | } | 80 | } |
| 82 | } | 81 | } |
| 83 | 82 | ||
| 84 | if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { | 83 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 85 | FS::CreateEmptyFile(path); | 84 | |
| 85 | if (!backing) { | ||
| 86 | return nullptr; | ||
| 86 | } | 87 | } |
| 87 | 88 | ||
| 88 | auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str()); | 89 | cache.insert_or_assign(path, std::move(backing)); |
| 89 | cache.insert_or_assign(path, backing); | ||
| 90 | 90 | ||
| 91 | // Cannot use make_shared as RealVfsFile constructor is private | 91 | // Cannot use make_shared as RealVfsFile constructor is private |
| 92 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); | 92 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); |
| @@ -94,25 +94,29 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | |||
| 94 | 94 | ||
| 95 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 95 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { |
| 96 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 96 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 97 | const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); | 97 | // Current usages of CreateFile expect to delete the contents of an existing file. |
| 98 | if (!FS::Exists(path)) { | 98 | if (FS::IsFile(path)) { |
| 99 | FS::CreateFullPath(path_fwd); | 99 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; |
| 100 | if (!FS::CreateEmptyFile(path)) { | 100 | |
| 101 | if (!temp.IsOpen()) { | ||
| 101 | return nullptr; | 102 | return nullptr; |
| 102 | } | 103 | } |
| 104 | |||
| 105 | temp.Close(); | ||
| 106 | |||
| 107 | return OpenFile(path, perms); | ||
| 108 | } | ||
| 109 | |||
| 110 | if (!FS::NewFile(path)) { | ||
| 111 | return nullptr; | ||
| 103 | } | 112 | } |
| 113 | |||
| 104 | return OpenFile(path, perms); | 114 | return OpenFile(path, perms); |
| 105 | } | 115 | } |
| 106 | 116 | ||
| 107 | VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { | 117 | VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { |
| 108 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 118 | // Unused |
| 109 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 119 | return nullptr; |
| 110 | |||
| 111 | if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || | ||
| 112 | !FS::Copy(old_path, new_path)) { | ||
| 113 | return nullptr; | ||
| 114 | } | ||
| 115 | return OpenFile(new_path, Mode::ReadWrite); | ||
| 116 | } | 120 | } |
| 117 | 121 | ||
| 118 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { | 122 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { |
| @@ -127,13 +131,13 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ | |||
| 127 | file->Close(); | 131 | file->Close(); |
| 128 | } | 132 | } |
| 129 | 133 | ||
| 130 | if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || | 134 | if (!FS::RenameFile(old_path, new_path)) { |
| 131 | !FS::Rename(old_path, new_path)) { | ||
| 132 | return nullptr; | 135 | return nullptr; |
| 133 | } | 136 | } |
| 134 | 137 | ||
| 135 | cache.erase(old_path); | 138 | cache.erase(old_path); |
| 136 | if (file->Open(new_path, "r+b")) { | 139 | file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); |
| 140 | if (file->IsOpen()) { | ||
| 137 | cache.insert_or_assign(new_path, std::move(file)); | 141 | cache.insert_or_assign(new_path, std::move(file)); |
| 138 | } else { | 142 | } else { |
| 139 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); | 143 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); |
| @@ -157,7 +161,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) { | |||
| 157 | cache.erase(path); | 161 | cache.erase(path); |
| 158 | } | 162 | } |
| 159 | 163 | ||
| 160 | return FS::Delete(path); | 164 | return FS::RemoveFile(path); |
| 161 | } | 165 | } |
| 162 | 166 | ||
| 163 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { | 167 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { |
| @@ -168,12 +172,8 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) | |||
| 168 | 172 | ||
| 169 | VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { | 173 | VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { |
| 170 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 174 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 171 | const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); | 175 | if (!FS::CreateDirs(path)) { |
| 172 | if (!FS::Exists(path)) { | 176 | return nullptr; |
| 173 | FS::CreateFullPath(path_fwd); | ||
| 174 | if (!FS::CreateDir(path)) { | ||
| 175 | return nullptr; | ||
| 176 | } | ||
| 177 | } | 177 | } |
| 178 | // Cannot use make_shared as RealVfsDirectory constructor is private | 178 | // Cannot use make_shared as RealVfsDirectory constructor is private |
| 179 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 179 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| @@ -181,13 +181,8 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms | |||
| 181 | 181 | ||
| 182 | VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, | 182 | VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, |
| 183 | std::string_view new_path_) { | 183 | std::string_view new_path_) { |
| 184 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 184 | // Unused |
| 185 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 185 | return nullptr; |
| 186 | if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) { | ||
| 187 | return nullptr; | ||
| 188 | } | ||
| 189 | FS::CopyDir(old_path, new_path); | ||
| 190 | return OpenDirectory(new_path, Mode::ReadWrite); | ||
| 191 | } | 186 | } |
| 192 | 187 | ||
| 193 | VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | 188 | VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, |
| @@ -195,8 +190,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 195 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 190 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); |
| 196 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 191 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); |
| 197 | 192 | ||
| 198 | if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || | 193 | if (!FS::RenameDir(old_path, new_path)) { |
| 199 | !FS::Rename(old_path, new_path)) { | ||
| 200 | return nullptr; | 194 | return nullptr; |
| 201 | } | 195 | } |
| 202 | 196 | ||
| @@ -208,7 +202,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 208 | 202 | ||
| 209 | const auto file_old_path = | 203 | const auto file_old_path = |
| 210 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); | 204 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); |
| 211 | auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), | 205 | auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), |
| 212 | FS::DirectorySeparator::PlatformDefault); | 206 | FS::DirectorySeparator::PlatformDefault); |
| 213 | const auto& cached = cache[file_old_path]; | 207 | const auto& cached = cache[file_old_path]; |
| 214 | 208 | ||
| @@ -218,7 +212,8 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 218 | 212 | ||
| 219 | auto file = cached.lock(); | 213 | auto file = cached.lock(); |
| 220 | cache.erase(file_old_path); | 214 | cache.erase(file_old_path); |
| 221 | if (file->Open(file_new_path, "r+b")) { | 215 | file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); |
| 216 | if (file->IsOpen()) { | ||
| 222 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); | 217 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); |
| 223 | } else { | 218 | } else { |
| 224 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); | 219 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); |
| @@ -245,15 +240,13 @@ bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | |||
| 245 | cache.erase(kv.first); | 240 | cache.erase(kv.first); |
| 246 | } | 241 | } |
| 247 | 242 | ||
| 248 | return FS::DeleteDirRecursively(path); | 243 | return FS::RemoveDirRecursively(path); |
| 249 | } | 244 | } |
| 250 | 245 | ||
| 251 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, | 246 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, |
| 252 | const std::string& path_, Mode perms_) | 247 | const std::string& path_, Mode perms_) |
| 253 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), | 248 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), |
| 254 | path_components(FS::SplitPathComponents(path_)), | 249 | path_components(FS::SplitPathComponents(path_)), perms(perms_) {} |
| 255 | parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), | ||
| 256 | perms(perms_) {} | ||
| 257 | 250 | ||
| 258 | RealVfsFile::~RealVfsFile() = default; | 251 | RealVfsFile::~RealVfsFile() = default; |
| 259 | 252 | ||
| @@ -266,7 +259,7 @@ std::size_t RealVfsFile::GetSize() const { | |||
| 266 | } | 259 | } |
| 267 | 260 | ||
| 268 | bool RealVfsFile::Resize(std::size_t new_size) { | 261 | bool RealVfsFile::Resize(std::size_t new_size) { |
| 269 | return backing->Resize(new_size); | 262 | return backing->SetSize(new_size); |
| 270 | } | 263 | } |
| 271 | 264 | ||
| 272 | VirtualDir RealVfsFile::GetContainingDirectory() const { | 265 | VirtualDir RealVfsFile::GetContainingDirectory() const { |
| @@ -274,33 +267,33 @@ VirtualDir RealVfsFile::GetContainingDirectory() const { | |||
| 274 | } | 267 | } |
| 275 | 268 | ||
| 276 | bool RealVfsFile::IsWritable() const { | 269 | bool RealVfsFile::IsWritable() const { |
| 277 | return True(perms & Mode::WriteAppend); | 270 | return True(perms & Mode::Write); |
| 278 | } | 271 | } |
| 279 | 272 | ||
| 280 | bool RealVfsFile::IsReadable() const { | 273 | bool RealVfsFile::IsReadable() const { |
| 281 | return True(perms & Mode::ReadWrite); | 274 | return True(perms & Mode::Read); |
| 282 | } | 275 | } |
| 283 | 276 | ||
| 284 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 277 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { |
| 285 | if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { | 278 | if (!backing->Seek(static_cast<s64>(offset))) { |
| 286 | return 0; | 279 | return 0; |
| 287 | } | 280 | } |
| 288 | return backing->ReadBytes(data, length); | 281 | return backing->ReadSpan(std::span{data, length}); |
| 289 | } | 282 | } |
| 290 | 283 | ||
| 291 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 284 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { |
| 292 | if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { | 285 | if (!backing->Seek(static_cast<s64>(offset))) { |
| 293 | return 0; | 286 | return 0; |
| 294 | } | 287 | } |
| 295 | return backing->WriteBytes(data, length); | 288 | return backing->WriteSpan(std::span{data, length}); |
| 296 | } | 289 | } |
| 297 | 290 | ||
| 298 | bool RealVfsFile::Rename(std::string_view name) { | 291 | bool RealVfsFile::Rename(std::string_view name) { |
| 299 | return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr; | 292 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; |
| 300 | } | 293 | } |
| 301 | 294 | ||
| 302 | bool RealVfsFile::Close() { | 295 | void RealVfsFile::Close() { |
| 303 | return backing->Close(); | 296 | backing->Close(); |
| 304 | } | 297 | } |
| 305 | 298 | ||
| 306 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if | 299 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if |
| @@ -313,15 +306,16 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>( | |||
| 313 | } | 306 | } |
| 314 | 307 | ||
| 315 | std::vector<VirtualFile> out; | 308 | std::vector<VirtualFile> out; |
| 316 | FS::ForeachDirectoryEntry( | 309 | |
| 317 | nullptr, path, | 310 | const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { |
| 318 | [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { | 311 | const auto full_path_string = FS::PathToUTF8String(full_path); |
| 319 | const std::string full_path = directory + DIR_SEP + filename; | 312 | |
| 320 | if (!FS::IsDirectory(full_path)) { | 313 | out.emplace_back(base.OpenFile(full_path_string, perms)); |
| 321 | out.emplace_back(base.OpenFile(full_path, perms)); | 314 | |
| 322 | } | 315 | return true; |
| 323 | return true; | 316 | }; |
| 324 | }); | 317 | |
| 318 | FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File); | ||
| 325 | 319 | ||
| 326 | return out; | 320 | return out; |
| 327 | } | 321 | } |
| @@ -333,42 +327,41 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi | |||
| 333 | } | 327 | } |
| 334 | 328 | ||
| 335 | std::vector<VirtualDir> out; | 329 | std::vector<VirtualDir> out; |
| 336 | FS::ForeachDirectoryEntry( | 330 | |
| 337 | nullptr, path, | 331 | const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { |
| 338 | [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { | 332 | const auto full_path_string = FS::PathToUTF8String(full_path); |
| 339 | const std::string full_path = directory + DIR_SEP + filename; | 333 | |
| 340 | if (FS::IsDirectory(full_path)) { | 334 | out.emplace_back(base.OpenDirectory(full_path_string, perms)); |
| 341 | out.emplace_back(base.OpenDirectory(full_path, perms)); | 335 | |
| 342 | } | 336 | return true; |
| 343 | return true; | 337 | }; |
| 344 | }); | 338 | |
| 339 | FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory); | ||
| 345 | 340 | ||
| 346 | return out; | 341 | return out; |
| 347 | } | 342 | } |
| 348 | 343 | ||
| 349 | RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) | 344 | RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) |
| 350 | : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), | 345 | : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), |
| 351 | path_components(FS::SplitPathComponents(path)), | 346 | path_components(FS::SplitPathComponents(path)), perms(perms_) { |
| 352 | parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), | 347 | if (!FS::Exists(path) && True(perms & Mode::Write)) { |
| 353 | perms(perms_) { | 348 | void(FS::CreateDirs(path)); |
| 354 | if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { | ||
| 355 | FS::CreateDir(path); | ||
| 356 | } | 349 | } |
| 357 | } | 350 | } |
| 358 | 351 | ||
| 359 | RealVfsDirectory::~RealVfsDirectory() = default; | 352 | RealVfsDirectory::~RealVfsDirectory() = default; |
| 360 | 353 | ||
| 361 | VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { | 354 | VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { |
| 362 | const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); | 355 | const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); |
| 363 | if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { | 356 | if (!FS::Exists(full_path) || FS::IsDir(full_path)) { |
| 364 | return nullptr; | 357 | return nullptr; |
| 365 | } | 358 | } |
| 366 | return base.OpenFile(full_path, perms); | 359 | return base.OpenFile(full_path, perms); |
| 367 | } | 360 | } |
| 368 | 361 | ||
| 369 | VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { | 362 | VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { |
| 370 | const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); | 363 | const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); |
| 371 | if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { | 364 | if (!FS::Exists(full_path) || !FS::IsDir(full_path)) { |
| 372 | return nullptr; | 365 | return nullptr; |
| 373 | } | 366 | } |
| 374 | return base.OpenDirectory(full_path, perms); | 367 | return base.OpenDirectory(full_path, perms); |
| @@ -383,17 +376,20 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { | |||
| 383 | } | 376 | } |
| 384 | 377 | ||
| 385 | VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { | 378 | VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { |
| 386 | const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); | 379 | const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); |
| 380 | if (!FS::CreateParentDirs(full_path)) { | ||
| 381 | return nullptr; | ||
| 382 | } | ||
| 387 | return base.CreateFile(full_path, perms); | 383 | return base.CreateFile(full_path, perms); |
| 388 | } | 384 | } |
| 389 | 385 | ||
| 390 | VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) { | 386 | VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) { |
| 391 | const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); | 387 | const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); |
| 392 | return base.CreateDirectory(full_path, perms); | 388 | return base.CreateDirectory(full_path, perms); |
| 393 | } | 389 | } |
| 394 | 390 | ||
| 395 | bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { | 391 | bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { |
| 396 | const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name)); | 392 | const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name)); |
| 397 | return base.DeleteDirectory(full_path); | 393 | return base.DeleteDirectory(full_path); |
| 398 | } | 394 | } |
| 399 | 395 | ||
| @@ -406,11 +402,11 @@ std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const { | |||
| 406 | } | 402 | } |
| 407 | 403 | ||
| 408 | bool RealVfsDirectory::IsWritable() const { | 404 | bool RealVfsDirectory::IsWritable() const { |
| 409 | return True(perms & Mode::WriteAppend); | 405 | return True(perms & Mode::Write); |
| 410 | } | 406 | } |
| 411 | 407 | ||
| 412 | bool RealVfsDirectory::IsReadable() const { | 408 | bool RealVfsDirectory::IsReadable() const { |
| 413 | return True(perms & Mode::ReadWrite); | 409 | return True(perms & Mode::Read); |
| 414 | } | 410 | } |
| 415 | 411 | ||
| 416 | std::string RealVfsDirectory::GetName() const { | 412 | std::string RealVfsDirectory::GetName() const { |
| @@ -426,27 +422,27 @@ VirtualDir RealVfsDirectory::GetParentDirectory() const { | |||
| 426 | } | 422 | } |
| 427 | 423 | ||
| 428 | VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { | 424 | VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { |
| 429 | const std::string subdir_path = (path + DIR_SEP).append(name); | 425 | const std::string subdir_path = (path + '/').append(name); |
| 430 | return base.CreateDirectory(subdir_path, perms); | 426 | return base.CreateDirectory(subdir_path, perms); |
| 431 | } | 427 | } |
| 432 | 428 | ||
| 433 | VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { | 429 | VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { |
| 434 | const std::string file_path = (path + DIR_SEP).append(name); | 430 | const std::string file_path = (path + '/').append(name); |
| 435 | return base.CreateFile(file_path, perms); | 431 | return base.CreateFile(file_path, perms); |
| 436 | } | 432 | } |
| 437 | 433 | ||
| 438 | bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { | 434 | bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { |
| 439 | const std::string subdir_path = (path + DIR_SEP).append(name); | 435 | const std::string subdir_path = (path + '/').append(name); |
| 440 | return base.DeleteDirectory(subdir_path); | 436 | return base.DeleteDirectory(subdir_path); |
| 441 | } | 437 | } |
| 442 | 438 | ||
| 443 | bool RealVfsDirectory::DeleteFile(std::string_view name) { | 439 | bool RealVfsDirectory::DeleteFile(std::string_view name) { |
| 444 | const std::string file_path = (path + DIR_SEP).append(name); | 440 | const std::string file_path = (path + '/').append(name); |
| 445 | return base.DeleteFile(file_path); | 441 | return base.DeleteFile(file_path); |
| 446 | } | 442 | } |
| 447 | 443 | ||
| 448 | bool RealVfsDirectory::Rename(std::string_view name) { | 444 | bool RealVfsDirectory::Rename(std::string_view name) { |
| 449 | const std::string new_name = (parent_path + DIR_SEP).append(name); | 445 | const std::string new_name = (parent_path + '/').append(name); |
| 450 | return base.MoveFile(path, new_name) != nullptr; | 446 | return base.MoveFile(path, new_name) != nullptr; |
| 451 | } | 447 | } |
| 452 | 448 | ||
| @@ -462,14 +458,17 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() | |||
| 462 | } | 458 | } |
| 463 | 459 | ||
| 464 | std::map<std::string, VfsEntryType, std::less<>> out; | 460 | std::map<std::string, VfsEntryType, std::less<>> out; |
| 465 | FS::ForeachDirectoryEntry( | 461 | |
| 466 | nullptr, path, | 462 | const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) { |
| 467 | [&out](u64* entries_out, const std::string& directory, const std::string& filename) { | 463 | const auto filename = FS::PathToUTF8String(full_path.filename()); |
| 468 | const std::string full_path = directory + DIR_SEP + filename; | 464 | |
| 469 | out.emplace(filename, | 465 | out.insert_or_assign(filename, |
| 470 | FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File); | 466 | FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File); |
| 471 | return true; | 467 | |
| 472 | }); | 468 | return true; |
| 469 | }; | ||
| 470 | |||
| 471 | FS::IterateDirEntries(path, callback); | ||
| 473 | 472 | ||
| 474 | return out; | 473 | return out; |
| 475 | } | 474 | } |
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 0666f2679..e4d1bba79 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h | |||
| @@ -61,14 +61,13 @@ private: | |||
| 61 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, | 61 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, |
| 62 | const std::string& path, Mode perms = Mode::Read); | 62 | const std::string& path, Mode perms = Mode::Read); |
| 63 | 63 | ||
| 64 | bool Close(); | 64 | void Close(); |
| 65 | 65 | ||
| 66 | RealVfsFilesystem& base; | 66 | RealVfsFilesystem& base; |
| 67 | std::shared_ptr<Common::FS::IOFile> backing; | 67 | std::shared_ptr<Common::FS::IOFile> backing; |
| 68 | std::string path; | 68 | std::string path; |
| 69 | std::string parent_path; | 69 | std::string parent_path; |
| 70 | std::vector<std::string> path_components; | 70 | std::vector<std::string> path_components; |
| 71 | std::vector<std::string> parent_components; | ||
| 72 | Mode perms; | 71 | Mode perms; |
| 73 | }; | 72 | }; |
| 74 | 73 | ||
| @@ -110,7 +109,6 @@ private: | |||
| 110 | std::string path; | 109 | std::string path; |
| 111 | std::string parent_path; | 110 | std::string parent_path; |
| 112 | std::vector<std::string> path_components; | 111 | std::vector<std::string> path_components; |
| 113 | std::vector<std::string> parent_components; | ||
| 114 | Mode perms; | 112 | Mode perms; |
| 115 | }; | 113 | }; |
| 116 | 114 | ||
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 814fd5680..d6fe1af47 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | #include <mbedtls/md.h> | 11 | #include <mbedtls/md.h> |
| 12 | #include <mbedtls/sha256.h> | 12 | #include <mbedtls/sha256.h> |
| 13 | 13 | ||
| 14 | #include "common/file_util.h" | 14 | #include "common/fs/path_util.h" |
| 15 | #include "common/hex_util.h" | 15 | #include "common/hex_util.h" |
| 16 | #include "common/string_util.h" | 16 | #include "common/string_util.h" |
| 17 | #include "core/crypto/aes_util.h" | 17 | #include "core/crypto/aes_util.h" |
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 497f35d23..61bda3786 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h | |||
| @@ -80,16 +80,12 @@ public: | |||
| 80 | 80 | ||
| 81 | memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); | 81 | memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); |
| 82 | 82 | ||
| 83 | ctx.ClearIncomingObjects(); | ||
| 84 | |||
| 85 | IPC::CommandHeader header{}; | 83 | IPC::CommandHeader header{}; |
| 86 | 84 | ||
| 87 | // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory | 85 | // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory |
| 88 | // padding. | 86 | // padding. |
| 89 | u32 raw_data_size = ctx.IsTipc() | 87 | u32 raw_data_size = ctx.write_size = |
| 90 | ? normal_params_size - 1 | 88 | ctx.IsTipc() ? normal_params_size - 1 : normal_params_size; |
| 91 | : sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; | ||
| 92 | |||
| 93 | u32 num_handles_to_move{}; | 89 | u32 num_handles_to_move{}; |
| 94 | u32 num_domain_objects{}; | 90 | u32 num_domain_objects{}; |
| 95 | const bool always_move_handles{ | 91 | const bool always_move_handles{ |
| @@ -101,16 +97,20 @@ public: | |||
| 101 | } | 97 | } |
| 102 | 98 | ||
| 103 | if (ctx.Session()->IsDomain()) { | 99 | if (ctx.Session()->IsDomain()) { |
| 104 | raw_data_size += static_cast<u32>(sizeof(DomainMessageHeader) / 4 + num_domain_objects); | 100 | raw_data_size += |
| 101 | static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects); | ||
| 102 | ctx.write_size += num_domain_objects; | ||
| 105 | } | 103 | } |
| 106 | 104 | ||
| 107 | if (ctx.IsTipc()) { | 105 | if (ctx.IsTipc()) { |
| 108 | header.type.Assign(ctx.GetCommandType()); | 106 | header.type.Assign(ctx.GetCommandType()); |
| 107 | } else { | ||
| 108 | raw_data_size += static_cast<u32>(sizeof(IPC::DataPayloadHeader) / sizeof(u32) + 4 + | ||
| 109 | normal_params_size); | ||
| 109 | } | 110 | } |
| 110 | 111 | ||
| 111 | ctx.data_size = static_cast<u32>(raw_data_size); | 112 | header.data_size.Assign(raw_data_size); |
| 112 | header.data_size.Assign(static_cast<u32>(raw_data_size)); | 113 | if (num_handles_to_copy || num_handles_to_move) { |
| 113 | if (num_handles_to_copy != 0 || num_handles_to_move != 0) { | ||
| 114 | header.enable_handle_descriptor.Assign(1); | 114 | header.enable_handle_descriptor.Assign(1); |
| 115 | } | 115 | } |
| 116 | PushRaw(header); | 116 | PushRaw(header); |
| @@ -143,7 +143,8 @@ public: | |||
| 143 | data_payload_index = index; | 143 | data_payload_index = index; |
| 144 | 144 | ||
| 145 | ctx.data_payload_offset = index; | 145 | ctx.data_payload_offset = index; |
| 146 | ctx.domain_offset = index + raw_data_size / 4; | 146 | ctx.write_size += index; |
| 147 | ctx.domain_offset = static_cast<u32>(index + raw_data_size / sizeof(u32)); | ||
| 147 | } | 148 | } |
| 148 | 149 | ||
| 149 | template <class T> | 150 | template <class T> |
| @@ -151,8 +152,8 @@ public: | |||
| 151 | if (context->Session()->IsDomain()) { | 152 | if (context->Session()->IsDomain()) { |
| 152 | context->AddDomainObject(std::move(iface)); | 153 | context->AddDomainObject(std::move(iface)); |
| 153 | } else { | 154 | } else { |
| 154 | // kernel.CurrentProcess()->GetResourceLimit()->Reserve( | 155 | kernel.CurrentProcess()->GetResourceLimit()->Reserve( |
| 155 | // Kernel::LimitableResource::Sessions, 1); | 156 | Kernel::LimitableResource::Sessions, 1); |
| 156 | 157 | ||
| 157 | auto* session = Kernel::KSession::Create(kernel); | 158 | auto* session = Kernel::KSession::Create(kernel); |
| 158 | session->Initialize(nullptr, iface->GetServiceName()); | 159 | session->Initialize(nullptr, iface->GetServiceName()); |
| @@ -167,24 +168,6 @@ public: | |||
| 167 | PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...)); | 168 | PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...)); |
| 168 | } | 169 | } |
| 169 | 170 | ||
| 170 | void ValidateHeader() { | ||
| 171 | const std::size_t num_domain_objects = context->NumDomainObjects(); | ||
| 172 | const std::size_t num_move_objects = context->NumMoveObjects(); | ||
| 173 | ASSERT_MSG(!num_domain_objects || !num_move_objects, | ||
| 174 | "cannot move normal handles and domain objects"); | ||
| 175 | ASSERT_MSG((index - data_payload_index) == normal_params_size, | ||
| 176 | "normal_params_size value is incorrect"); | ||
| 177 | ASSERT_MSG((num_domain_objects + num_move_objects) == num_objects_to_move, | ||
| 178 | "num_objects_to_move value is incorrect"); | ||
| 179 | ASSERT_MSG(context->NumCopyObjects() == num_handles_to_copy, | ||
| 180 | "num_handles_to_copy value is incorrect"); | ||
| 181 | } | ||
| 182 | |||
| 183 | // Validate on destruction, as there shouldn't be any case where we don't want it | ||
| 184 | ~ResponseBuilder() { | ||
| 185 | ValidateHeader(); | ||
| 186 | } | ||
| 187 | |||
| 188 | void PushImpl(s8 value); | 171 | void PushImpl(s8 value); |
| 189 | void PushImpl(s16 value); | 172 | void PushImpl(s16 value); |
| 190 | void PushImpl(s32 value); | 173 | void PushImpl(s32 value); |
| @@ -404,7 +387,7 @@ public: | |||
| 404 | std::shared_ptr<T> PopIpcInterface() { | 387 | std::shared_ptr<T> PopIpcInterface() { |
| 405 | ASSERT(context->Session()->IsDomain()); | 388 | ASSERT(context->Session()->IsDomain()); |
| 406 | ASSERT(context->GetDomainMessageHeader().input_object_count > 0); | 389 | ASSERT(context->GetDomainMessageHeader().input_object_count > 0); |
| 407 | return context->GetDomainRequestHandler<T>(Pop<u32>() - 1); | 390 | return context->GetDomainHandler<T>(Pop<u32>() - 1); |
| 408 | } | 391 | } |
| 409 | }; | 392 | }; |
| 410 | 393 | ||
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index ce3466df8..9d069a78f 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp | |||
| @@ -35,11 +35,11 @@ SessionRequestHandler::SessionRequestHandler() = default; | |||
| 35 | SessionRequestHandler::~SessionRequestHandler() = default; | 35 | SessionRequestHandler::~SessionRequestHandler() = default; |
| 36 | 36 | ||
| 37 | void SessionRequestHandler::ClientConnected(KServerSession* session) { | 37 | void SessionRequestHandler::ClientConnected(KServerSession* session) { |
| 38 | session->SetHleHandler(shared_from_this()); | 38 | session->SetSessionHandler(shared_from_this()); |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | void SessionRequestHandler::ClientDisconnected(KServerSession* session) { | 41 | void SessionRequestHandler::ClientDisconnected(KServerSession* session) { |
| 42 | session->SetHleHandler(nullptr); | 42 | session->SetSessionHandler(nullptr); |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, | 45 | HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, |
| @@ -64,19 +64,15 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 | |||
| 64 | if (command_header->enable_handle_descriptor) { | 64 | if (command_header->enable_handle_descriptor) { |
| 65 | handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>(); | 65 | handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>(); |
| 66 | if (handle_descriptor_header->send_current_pid) { | 66 | if (handle_descriptor_header->send_current_pid) { |
| 67 | rp.Skip(2, false); | 67 | pid = rp.Pop<u64>(); |
| 68 | } | 68 | } |
| 69 | if (incoming) { | 69 | if (incoming) { |
| 70 | // Populate the object lists with the data in the IPC request. | 70 | // Populate the object lists with the data in the IPC request. |
| 71 | for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { | 71 | for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { |
| 72 | const u32 copy_handle{rp.Pop<Handle>()}; | 72 | incoming_copy_handles.push_back(rp.Pop<Handle>()); |
| 73 | copy_handles.push_back(copy_handle); | ||
| 74 | copy_objects.push_back(handle_table.GetObject(copy_handle).GetPointerUnsafe()); | ||
| 75 | } | 73 | } |
| 76 | for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { | 74 | for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { |
| 77 | const u32 move_handle{rp.Pop<Handle>()}; | 75 | incoming_move_handles.push_back(rp.Pop<Handle>()); |
| 78 | move_handles.push_back(move_handle); | ||
| 79 | move_objects.push_back(handle_table.GetObject(move_handle).GetPointerUnsafe()); | ||
| 80 | } | 76 | } |
| 81 | } else { | 77 | } else { |
| 82 | // For responses we just ignore the handles, they're empty and will be populated when | 78 | // For responses we just ignore the handles, they're empty and will be populated when |
| @@ -86,16 +82,16 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 | |||
| 86 | } | 82 | } |
| 87 | } | 83 | } |
| 88 | 84 | ||
| 89 | for (unsigned i = 0; i < command_header->num_buf_x_descriptors; ++i) { | 85 | for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { |
| 90 | buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>()); | 86 | buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>()); |
| 91 | } | 87 | } |
| 92 | for (unsigned i = 0; i < command_header->num_buf_a_descriptors; ++i) { | 88 | for (u32 i = 0; i < command_header->num_buf_a_descriptors; ++i) { |
| 93 | buffer_a_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | 89 | buffer_a_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); |
| 94 | } | 90 | } |
| 95 | for (unsigned i = 0; i < command_header->num_buf_b_descriptors; ++i) { | 91 | for (u32 i = 0; i < command_header->num_buf_b_descriptors; ++i) { |
| 96 | buffer_b_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | 92 | buffer_b_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); |
| 97 | } | 93 | } |
| 98 | for (unsigned i = 0; i < command_header->num_buf_w_descriptors; ++i) { | 94 | for (u32 i = 0; i < command_header->num_buf_w_descriptors; ++i) { |
| 99 | buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); | 95 | buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); |
| 100 | } | 96 | } |
| 101 | 97 | ||
| @@ -148,14 +144,14 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 | |||
| 148 | IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { | 144 | IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { |
| 149 | buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); | 145 | buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); |
| 150 | } else { | 146 | } else { |
| 151 | unsigned num_buf_c_descriptors = | 147 | u32 num_buf_c_descriptors = |
| 152 | static_cast<unsigned>(command_header->buf_c_descriptor_flags.Value()) - 2; | 148 | static_cast<u32>(command_header->buf_c_descriptor_flags.Value()) - 2; |
| 153 | 149 | ||
| 154 | // This is used to detect possible underflows, in case something is broken | 150 | // This is used to detect possible underflows, in case something is broken |
| 155 | // with the two ifs above and the flags value is == 0 || == 1. | 151 | // with the two ifs above and the flags value is == 0 || == 1. |
| 156 | ASSERT(num_buf_c_descriptors < 14); | 152 | ASSERT(num_buf_c_descriptors < 14); |
| 157 | 153 | ||
| 158 | for (unsigned i = 0; i < num_buf_c_descriptors; ++i) { | 154 | for (u32 i = 0; i < num_buf_c_descriptors; ++i) { |
| 159 | buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); | 155 | buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); |
| 160 | } | 156 | } |
| 161 | } | 157 | } |
| @@ -186,26 +182,14 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t | |||
| 186 | auto& owner_process = *requesting_thread.GetOwnerProcess(); | 182 | auto& owner_process = *requesting_thread.GetOwnerProcess(); |
| 187 | auto& handle_table = owner_process.GetHandleTable(); | 183 | auto& handle_table = owner_process.GetHandleTable(); |
| 188 | 184 | ||
| 189 | // The data_size already includes the payload header, the padding and the domain header. | 185 | for (auto& object : outgoing_copy_objects) { |
| 190 | std::size_t size{}; | ||
| 191 | |||
| 192 | if (IsTipc()) { | ||
| 193 | size = cmd_buf.size(); | ||
| 194 | } else { | ||
| 195 | size = data_payload_offset + data_size - sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; | ||
| 196 | if (Session()->IsDomain()) { | ||
| 197 | size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | for (auto& object : copy_objects) { | ||
| 202 | Handle handle{}; | 186 | Handle handle{}; |
| 203 | if (object) { | 187 | if (object) { |
| 204 | R_TRY(handle_table.Add(&handle, object)); | 188 | R_TRY(handle_table.Add(&handle, object)); |
| 205 | } | 189 | } |
| 206 | cmd_buf[current_offset++] = handle; | 190 | cmd_buf[current_offset++] = handle; |
| 207 | } | 191 | } |
| 208 | for (auto& object : move_objects) { | 192 | for (auto& object : outgoing_move_objects) { |
| 209 | Handle handle{}; | 193 | Handle handle{}; |
| 210 | if (object) { | 194 | if (object) { |
| 211 | R_TRY(handle_table.Add(&handle, object)); | 195 | R_TRY(handle_table.Add(&handle, object)); |
| @@ -220,9 +204,9 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t | |||
| 220 | // TODO(Subv): This completely ignores C buffers. | 204 | // TODO(Subv): This completely ignores C buffers. |
| 221 | 205 | ||
| 222 | if (Session()->IsDomain()) { | 206 | if (Session()->IsDomain()) { |
| 223 | current_offset = domain_offset - static_cast<u32>(domain_objects.size()); | 207 | current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size()); |
| 224 | for (const auto& object : domain_objects) { | 208 | for (const auto& object : outgoing_domain_objects) { |
| 225 | server_session->AppendDomainRequestHandler(object); | 209 | server_session->AppendDomainHandler(object); |
| 226 | cmd_buf[current_offset++] = | 210 | cmd_buf[current_offset++] = |
| 227 | static_cast<u32_le>(server_session->NumDomainRequestHandlers()); | 211 | static_cast<u32_le>(server_session->NumDomainRequestHandlers()); |
| 228 | } | 212 | } |
| @@ -230,7 +214,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t | |||
| 230 | 214 | ||
| 231 | // Copy the translated command buffer back into the thread's command buffer area. | 215 | // Copy the translated command buffer back into the thread's command buffer area. |
| 232 | memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), cmd_buf.data(), | 216 | memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), cmd_buf.data(), |
| 233 | size * sizeof(u32)); | 217 | write_size * sizeof(u32)); |
| 234 | 218 | ||
| 235 | return RESULT_SUCCESS; | 219 | return RESULT_SUCCESS; |
| 236 | } | 220 | } |
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 4fba300dc..b47e363cc 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h | |||
| @@ -11,7 +11,8 @@ | |||
| 11 | #include <string> | 11 | #include <string> |
| 12 | #include <type_traits> | 12 | #include <type_traits> |
| 13 | #include <vector> | 13 | #include <vector> |
| 14 | #include <boost/container/small_vector.hpp> | 14 | |
| 15 | #include "common/assert.h" | ||
| 15 | #include "common/common_types.h" | 16 | #include "common/common_types.h" |
| 16 | #include "common/concepts.h" | 17 | #include "common/concepts.h" |
| 17 | #include "common/swap.h" | 18 | #include "common/swap.h" |
| @@ -84,6 +85,69 @@ public: | |||
| 84 | void ClientDisconnected(KServerSession* session); | 85 | void ClientDisconnected(KServerSession* session); |
| 85 | }; | 86 | }; |
| 86 | 87 | ||
| 88 | using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Manages the underlying HLE requests for a session, and whether (or not) the session should be | ||
| 92 | * treated as a domain. This is managed separately from server sessions, as this state is shared | ||
| 93 | * when objects are cloned. | ||
| 94 | */ | ||
| 95 | class SessionRequestManager final { | ||
| 96 | public: | ||
| 97 | SessionRequestManager() = default; | ||
| 98 | |||
| 99 | bool IsDomain() const { | ||
| 100 | return is_domain; | ||
| 101 | } | ||
| 102 | |||
| 103 | void ConvertToDomain() { | ||
| 104 | domain_handlers = {session_handler}; | ||
| 105 | is_domain = true; | ||
| 106 | } | ||
| 107 | |||
| 108 | std::size_t DomainHandlerCount() const { | ||
| 109 | return domain_handlers.size(); | ||
| 110 | } | ||
| 111 | |||
| 112 | bool HasSessionHandler() const { | ||
| 113 | return session_handler != nullptr; | ||
| 114 | } | ||
| 115 | |||
| 116 | SessionRequestHandler& SessionHandler() { | ||
| 117 | return *session_handler; | ||
| 118 | } | ||
| 119 | |||
| 120 | const SessionRequestHandler& SessionHandler() const { | ||
| 121 | return *session_handler; | ||
| 122 | } | ||
| 123 | |||
| 124 | void CloseDomainHandler(std::size_t index) { | ||
| 125 | if (index < DomainHandlerCount()) { | ||
| 126 | domain_handlers[index] = nullptr; | ||
| 127 | } else { | ||
| 128 | UNREACHABLE_MSG("Unexpected handler index {}", index); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | SessionRequestHandlerPtr DomainHandler(std::size_t index) const { | ||
| 133 | ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index); | ||
| 134 | return domain_handlers.at(index); | ||
| 135 | } | ||
| 136 | |||
| 137 | void AppendDomainHandler(SessionRequestHandlerPtr&& handler) { | ||
| 138 | domain_handlers.emplace_back(std::move(handler)); | ||
| 139 | } | ||
| 140 | |||
| 141 | void SetSessionHandler(SessionRequestHandlerPtr&& handler) { | ||
| 142 | session_handler = std::move(handler); | ||
| 143 | } | ||
| 144 | |||
| 145 | private: | ||
| 146 | bool is_domain{}; | ||
| 147 | SessionRequestHandlerPtr session_handler; | ||
| 148 | std::vector<SessionRequestHandlerPtr> domain_handlers; | ||
| 149 | }; | ||
| 150 | |||
| 87 | /** | 151 | /** |
| 88 | * Class containing information about an in-flight IPC request being handled by an HLE service | 152 | * Class containing information about an in-flight IPC request being handled by an HLE service |
| 89 | * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and | 153 | * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and |
| @@ -150,6 +214,10 @@ public: | |||
| 150 | return command_header->type; | 214 | return command_header->type; |
| 151 | } | 215 | } |
| 152 | 216 | ||
| 217 | u64 GetPID() const { | ||
| 218 | return pid; | ||
| 219 | } | ||
| 220 | |||
| 153 | u32 GetDataPayloadOffset() const { | 221 | u32 GetDataPayloadOffset() const { |
| 154 | return data_payload_offset; | 222 | return data_payload_offset; |
| 155 | } | 223 | } |
| @@ -220,53 +288,32 @@ public: | |||
| 220 | bool CanWriteBuffer(std::size_t buffer_index = 0) const; | 288 | bool CanWriteBuffer(std::size_t buffer_index = 0) const; |
| 221 | 289 | ||
| 222 | Handle GetCopyHandle(std::size_t index) const { | 290 | Handle GetCopyHandle(std::size_t index) const { |
| 223 | return copy_handles.at(index); | 291 | return incoming_copy_handles.at(index); |
| 224 | } | 292 | } |
| 225 | 293 | ||
| 226 | Handle GetMoveHandle(std::size_t index) const { | 294 | Handle GetMoveHandle(std::size_t index) const { |
| 227 | return move_handles.at(index); | 295 | return incoming_move_handles.at(index); |
| 228 | } | 296 | } |
| 229 | 297 | ||
| 230 | void AddMoveObject(KAutoObject* object) { | 298 | void AddMoveObject(KAutoObject* object) { |
| 231 | move_objects.emplace_back(object); | 299 | outgoing_move_objects.emplace_back(object); |
| 232 | } | 300 | } |
| 233 | 301 | ||
| 234 | void AddCopyObject(KAutoObject* object) { | 302 | void AddCopyObject(KAutoObject* object) { |
| 235 | copy_objects.emplace_back(object); | 303 | outgoing_copy_objects.emplace_back(object); |
| 236 | } | 304 | } |
| 237 | 305 | ||
| 238 | void AddDomainObject(std::shared_ptr<SessionRequestHandler> object) { | 306 | void AddDomainObject(SessionRequestHandlerPtr object) { |
| 239 | domain_objects.emplace_back(std::move(object)); | 307 | outgoing_domain_objects.emplace_back(std::move(object)); |
| 240 | } | 308 | } |
| 241 | 309 | ||
| 242 | template <typename T> | 310 | template <typename T> |
| 243 | std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const { | 311 | std::shared_ptr<T> GetDomainHandler(std::size_t index) const { |
| 244 | return std::static_pointer_cast<T>(domain_request_handlers.at(index)); | 312 | return std::static_pointer_cast<T>(manager->DomainHandler(index)); |
| 245 | } | 313 | } |
| 246 | 314 | ||
| 247 | void SetDomainRequestHandlers( | 315 | void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) { |
| 248 | const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) { | 316 | manager = std::move(manager_); |
| 249 | domain_request_handlers = handlers; | ||
| 250 | } | ||
| 251 | |||
| 252 | /// Clears the list of objects so that no lingering objects are written accidentally to the | ||
| 253 | /// response buffer. | ||
| 254 | void ClearIncomingObjects() { | ||
| 255 | move_objects.clear(); | ||
| 256 | copy_objects.clear(); | ||
| 257 | domain_objects.clear(); | ||
| 258 | } | ||
| 259 | |||
| 260 | std::size_t NumMoveObjects() const { | ||
| 261 | return move_objects.size(); | ||
| 262 | } | ||
| 263 | |||
| 264 | std::size_t NumCopyObjects() const { | ||
| 265 | return copy_objects.size(); | ||
| 266 | } | ||
| 267 | |||
| 268 | std::size_t NumDomainObjects() const { | ||
| 269 | return domain_objects.size(); | ||
| 270 | } | 317 | } |
| 271 | 318 | ||
| 272 | std::string Description() const; | 319 | std::string Description() const; |
| @@ -288,12 +335,12 @@ private: | |||
| 288 | Kernel::KServerSession* server_session{}; | 335 | Kernel::KServerSession* server_session{}; |
| 289 | KThread* thread; | 336 | KThread* thread; |
| 290 | 337 | ||
| 291 | // TODO(yuriks): Check common usage of this and optimize size accordingly | 338 | std::vector<Handle> incoming_move_handles; |
| 292 | boost::container::small_vector<Handle, 8> move_handles; | 339 | std::vector<Handle> incoming_copy_handles; |
| 293 | boost::container::small_vector<Handle, 8> copy_handles; | 340 | |
| 294 | boost::container::small_vector<KAutoObject*, 8> move_objects; | 341 | std::vector<KAutoObject*> outgoing_move_objects; |
| 295 | boost::container::small_vector<KAutoObject*, 8> copy_objects; | 342 | std::vector<KAutoObject*> outgoing_copy_objects; |
| 296 | boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects; | 343 | std::vector<SessionRequestHandlerPtr> outgoing_domain_objects; |
| 297 | 344 | ||
| 298 | std::optional<IPC::CommandHeader> command_header; | 345 | std::optional<IPC::CommandHeader> command_header; |
| 299 | std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header; | 346 | std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header; |
| @@ -305,13 +352,14 @@ private: | |||
| 305 | std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors; | 352 | std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors; |
| 306 | std::vector<IPC::BufferDescriptorC> buffer_c_desciptors; | 353 | std::vector<IPC::BufferDescriptorC> buffer_c_desciptors; |
| 307 | 354 | ||
| 355 | u32_le command{}; | ||
| 356 | u64 pid{}; | ||
| 357 | u32 write_size{}; | ||
| 308 | u32 data_payload_offset{}; | 358 | u32 data_payload_offset{}; |
| 309 | u32 handles_offset{}; | 359 | u32 handles_offset{}; |
| 310 | u32 domain_offset{}; | 360 | u32 domain_offset{}; |
| 311 | u32 data_size{}; | ||
| 312 | u32_le command{}; | ||
| 313 | 361 | ||
| 314 | std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; | 362 | std::shared_ptr<SessionRequestManager> manager; |
| 315 | bool is_thread_waiting{}; | 363 | bool is_thread_waiting{}; |
| 316 | 364 | ||
| 317 | KernelCore& kernel; | 365 | KernelCore& kernel; |
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index 69ae405e6..10edede17 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp | |||
| @@ -70,14 +70,22 @@ constexpr size_t SlabCountExtraKThread = 160; | |||
| 70 | template <typename T> | 70 | template <typename T> |
| 71 | VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAddr address, | 71 | VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAddr address, |
| 72 | size_t num_objects) { | 72 | size_t num_objects) { |
| 73 | // TODO(bunnei): This is just a place holder. We should initialize the appropriate KSlabHeap for | ||
| 74 | // kernel object type T with the backing kernel memory pointer once we emulate kernel memory. | ||
| 75 | |||
| 73 | const size_t size = Common::AlignUp(sizeof(T) * num_objects, alignof(void*)); | 76 | const size_t size = Common::AlignUp(sizeof(T) * num_objects, alignof(void*)); |
| 74 | VAddr start = Common::AlignUp(address, alignof(T)); | 77 | VAddr start = Common::AlignUp(address, alignof(T)); |
| 75 | 78 | ||
| 79 | // This is intentionally empty. Once KSlabHeap is fully implemented, we can replace this with | ||
| 80 | // the pointer to emulated memory to pass along. Until then, KSlabHeap will just allocate/free | ||
| 81 | // host memory. | ||
| 82 | void* backing_kernel_memory{}; | ||
| 83 | |||
| 76 | if (size > 0) { | 84 | if (size > 0) { |
| 77 | const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1); | 85 | const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1); |
| 78 | ASSERT(region != nullptr); | 86 | ASSERT(region != nullptr); |
| 79 | ASSERT(region->IsDerivedFrom(KMemoryRegionType_KernelSlab)); | 87 | ASSERT(region->IsDerivedFrom(KMemoryRegionType_KernelSlab)); |
| 80 | T::InitializeSlabHeap(system.Kernel(), system.Memory().GetKernelBuffer(start, size), size); | 88 | T::InitializeSlabHeap(system.Kernel(), backing_kernel_memory, size); |
| 81 | } | 89 | } |
| 82 | 90 | ||
| 83 | return start + size; | 91 | return start + size; |
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp index ad01cf67e..4a12dee10 100644 --- a/src/core/hle/kernel/k_client_port.cpp +++ b/src/core/hle/kernel/k_client_port.cpp | |||
| @@ -58,9 +58,9 @@ bool KClientPort::IsSignaled() const { | |||
| 58 | 58 | ||
| 59 | ResultCode KClientPort::CreateSession(KClientSession** out) { | 59 | ResultCode KClientPort::CreateSession(KClientSession** out) { |
| 60 | // Reserve a new session from the resource limit. | 60 | // Reserve a new session from the resource limit. |
| 61 | // KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), | 61 | KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), |
| 62 | // LimitableResource::Sessions); | 62 | LimitableResource::Sessions); |
| 63 | // R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); | 63 | R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); |
| 64 | 64 | ||
| 65 | // Update the session counts. | 65 | // Update the session counts. |
| 66 | { | 66 | { |
| @@ -104,7 +104,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out) { | |||
| 104 | session->Initialize(this, parent->GetName()); | 104 | session->Initialize(this, parent->GetName()); |
| 105 | 105 | ||
| 106 | // Commit the session reservation. | 106 | // Commit the session reservation. |
| 107 | // session_reservation.Commit(); | 107 | session_reservation.Commit(); |
| 108 | 108 | ||
| 109 | // Register the session. | 109 | // Register the session. |
| 110 | KSession::Register(kernel, session); | 110 | KSession::Register(kernel, session); |
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h index d00ce3ddd..8501156e8 100644 --- a/src/core/hle/kernel/k_client_port.h +++ b/src/core/hle/kernel/k_client_port.h | |||
| @@ -31,6 +31,9 @@ public: | |||
| 31 | const KPort* GetParent() const { | 31 | const KPort* GetParent() const { |
| 32 | return parent; | 32 | return parent; |
| 33 | } | 33 | } |
| 34 | KPort* GetParent() { | ||
| 35 | return parent; | ||
| 36 | } | ||
| 34 | 37 | ||
| 35 | s32 GetNumSessions() const { | 38 | s32 GetNumSessions() const { |
| 36 | return num_sessions; | 39 | return num_sessions; |
diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp index feb2bb11f..223c0b205 100644 --- a/src/core/hle/kernel/k_port.cpp +++ b/src/core/hle/kernel/k_port.cpp | |||
| @@ -56,11 +56,8 @@ ResultCode KPort::EnqueueSession(KServerSession* session) { | |||
| 56 | 56 | ||
| 57 | R_UNLESS(state == State::Normal, ResultPortClosed); | 57 | R_UNLESS(state == State::Normal, ResultPortClosed); |
| 58 | 58 | ||
| 59 | if (server.HasHLEHandler()) { | 59 | server.EnqueueSession(session); |
| 60 | server.GetHLEHandler()->ClientConnected(session); | 60 | server.GetSessionRequestHandler()->ClientConnected(server.AcceptSession()); |
| 61 | } else { | ||
| 62 | server.EnqueueSession(session); | ||
| 63 | } | ||
| 64 | 61 | ||
| 65 | return RESULT_SUCCESS; | 62 | return RESULT_SUCCESS; |
| 66 | } | 63 | } |
diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h index e76792253..d1a757ec3 100644 --- a/src/core/hle/kernel/k_server_port.h +++ b/src/core/hle/kernel/k_server_port.h | |||
| @@ -32,26 +32,24 @@ public: | |||
| 32 | explicit KServerPort(KernelCore& kernel_); | 32 | explicit KServerPort(KernelCore& kernel_); |
| 33 | virtual ~KServerPort() override; | 33 | virtual ~KServerPort() override; |
| 34 | 34 | ||
| 35 | using HLEHandler = std::shared_ptr<SessionRequestHandler>; | ||
| 36 | |||
| 37 | void Initialize(KPort* parent_, std::string&& name_); | 35 | void Initialize(KPort* parent_, std::string&& name_); |
| 38 | 36 | ||
| 39 | /// Whether or not this server port has an HLE handler available. | 37 | /// Whether or not this server port has an HLE handler available. |
| 40 | bool HasHLEHandler() const { | 38 | bool HasSessionRequestHandler() const { |
| 41 | return hle_handler != nullptr; | 39 | return session_handler != nullptr; |
| 42 | } | 40 | } |
| 43 | 41 | ||
| 44 | /// Gets the HLE handler for this port. | 42 | /// Gets the HLE handler for this port. |
| 45 | HLEHandler GetHLEHandler() const { | 43 | SessionRequestHandlerPtr GetSessionRequestHandler() const { |
| 46 | return hle_handler; | 44 | return session_handler; |
| 47 | } | 45 | } |
| 48 | 46 | ||
| 49 | /** | 47 | /** |
| 50 | * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port | 48 | * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port |
| 51 | * will inherit a reference to this handler. | 49 | * will inherit a reference to this handler. |
| 52 | */ | 50 | */ |
| 53 | void SetHleHandler(HLEHandler hle_handler_) { | 51 | void SetSessionHandler(SessionRequestHandlerPtr&& handler) { |
| 54 | hle_handler = std::move(hle_handler_); | 52 | session_handler = std::move(handler); |
| 55 | } | 53 | } |
| 56 | 54 | ||
| 57 | void EnqueueSession(KServerSession* pending_session); | 55 | void EnqueueSession(KServerSession* pending_session); |
| @@ -73,7 +71,7 @@ private: | |||
| 73 | 71 | ||
| 74 | private: | 72 | private: |
| 75 | SessionList session_list; | 73 | SessionList session_list; |
| 76 | HLEHandler hle_handler; | 74 | SessionRequestHandlerPtr session_handler; |
| 77 | KPort* parent{}; | 75 | KPort* parent{}; |
| 78 | }; | 76 | }; |
| 79 | 77 | ||
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index 8850d9af5..457fdfd60 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp | |||
| @@ -23,7 +23,8 @@ | |||
| 23 | 23 | ||
| 24 | namespace Kernel { | 24 | namespace Kernel { |
| 25 | 25 | ||
| 26 | KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} | 26 | KServerSession::KServerSession(KernelCore& kernel_) |
| 27 | : KSynchronizationObject{kernel_}, manager{std::make_shared<SessionRequestManager>()} {} | ||
| 27 | 28 | ||
| 28 | KServerSession::~KServerSession() { | 29 | KServerSession::~KServerSession() { |
| 29 | kernel.ReleaseServiceThread(service_thread); | 30 | kernel.ReleaseServiceThread(service_thread); |
| @@ -43,14 +44,8 @@ void KServerSession::Destroy() { | |||
| 43 | } | 44 | } |
| 44 | 45 | ||
| 45 | void KServerSession::OnClientClosed() { | 46 | void KServerSession::OnClientClosed() { |
| 46 | // We keep a shared pointer to the hle handler to keep it alive throughout | 47 | if (manager->HasSessionHandler()) { |
| 47 | // the call to ClientDisconnected, as ClientDisconnected invalidates the | 48 | manager->SessionHandler().ClientDisconnected(this); |
| 48 | // hle_handler member itself during the course of the function executing. | ||
| 49 | std::shared_ptr<SessionRequestHandler> handler = hle_handler; | ||
| 50 | if (handler) { | ||
| 51 | // Note that after this returns, this server session's hle_handler is | ||
| 52 | // invalidated (set to null). | ||
| 53 | handler->ClientDisconnected(this); | ||
| 54 | } | 49 | } |
| 55 | } | 50 | } |
| 56 | 51 | ||
| @@ -66,12 +61,12 @@ bool KServerSession::IsSignaled() const { | |||
| 66 | return false; | 61 | return false; |
| 67 | } | 62 | } |
| 68 | 63 | ||
| 69 | void KServerSession::AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler) { | 64 | void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) { |
| 70 | domain_request_handlers.push_back(std::move(handler)); | 65 | manager->AppendDomainHandler(std::move(handler)); |
| 71 | } | 66 | } |
| 72 | 67 | ||
| 73 | std::size_t KServerSession::NumDomainRequestHandlers() const { | 68 | std::size_t KServerSession::NumDomainRequestHandlers() const { |
| 74 | return domain_request_handlers.size(); | 69 | return manager->DomainHandlerCount(); |
| 75 | } | 70 | } |
| 76 | 71 | ||
| 77 | ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { | 72 | ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { |
| @@ -80,14 +75,14 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co | |||
| 80 | } | 75 | } |
| 81 | 76 | ||
| 82 | // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs | 77 | // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs |
| 83 | context.SetDomainRequestHandlers(domain_request_handlers); | 78 | context.SetSessionRequestManager(manager); |
| 84 | 79 | ||
| 85 | // If there is a DomainMessageHeader, then this is CommandType "Request" | 80 | // If there is a DomainMessageHeader, then this is CommandType "Request" |
| 86 | const auto& domain_message_header = context.GetDomainMessageHeader(); | 81 | const auto& domain_message_header = context.GetDomainMessageHeader(); |
| 87 | const u32 object_id{domain_message_header.object_id}; | 82 | const u32 object_id{domain_message_header.object_id}; |
| 88 | switch (domain_message_header.command) { | 83 | switch (domain_message_header.command) { |
| 89 | case IPC::DomainMessageHeader::CommandType::SendMessage: | 84 | case IPC::DomainMessageHeader::CommandType::SendMessage: |
| 90 | if (object_id > domain_request_handlers.size()) { | 85 | if (object_id > manager->DomainHandlerCount()) { |
| 91 | LOG_CRITICAL(IPC, | 86 | LOG_CRITICAL(IPC, |
| 92 | "object_id {} is too big! This probably means a recent service call " | 87 | "object_id {} is too big! This probably means a recent service call " |
| 93 | "to {} needed to return a new interface!", | 88 | "to {} needed to return a new interface!", |
| @@ -95,12 +90,12 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co | |||
| 95 | UNREACHABLE(); | 90 | UNREACHABLE(); |
| 96 | return RESULT_SUCCESS; // Ignore error if asserts are off | 91 | return RESULT_SUCCESS; // Ignore error if asserts are off |
| 97 | } | 92 | } |
| 98 | return domain_request_handlers[object_id - 1]->HandleSyncRequest(*this, context); | 93 | return manager->DomainHandler(object_id - 1)->HandleSyncRequest(*this, context); |
| 99 | 94 | ||
| 100 | case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { | 95 | case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { |
| 101 | LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); | 96 | LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); |
| 102 | 97 | ||
| 103 | domain_request_handlers[object_id - 1] = nullptr; | 98 | manager->CloseDomainHandler(object_id - 1); |
| 104 | 99 | ||
| 105 | IPC::ResponseBuilder rb{context, 2}; | 100 | IPC::ResponseBuilder rb{context, 2}; |
| 106 | rb.Push(RESULT_SUCCESS); | 101 | rb.Push(RESULT_SUCCESS); |
| @@ -133,14 +128,14 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) { | |||
| 133 | if (IsDomain() && context.HasDomainMessageHeader()) { | 128 | if (IsDomain() && context.HasDomainMessageHeader()) { |
| 134 | result = HandleDomainSyncRequest(context); | 129 | result = HandleDomainSyncRequest(context); |
| 135 | // If there is no domain header, the regular session handler is used | 130 | // If there is no domain header, the regular session handler is used |
| 136 | } else if (hle_handler != nullptr) { | 131 | } else if (manager->HasSessionHandler()) { |
| 137 | // If this ServerSession has an associated HLE handler, forward the request to it. | 132 | // If this ServerSession has an associated HLE handler, forward the request to it. |
| 138 | result = hle_handler->HandleSyncRequest(*this, context); | 133 | result = manager->SessionHandler().HandleSyncRequest(*this, context); |
| 139 | } | 134 | } |
| 140 | 135 | ||
| 141 | if (convert_to_domain) { | 136 | if (convert_to_domain) { |
| 142 | ASSERT_MSG(IsSession(), "ServerSession is already a domain instance."); | 137 | ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance."); |
| 143 | domain_request_handlers = {hle_handler}; | 138 | manager->ConvertToDomain(); |
| 144 | convert_to_domain = false; | 139 | convert_to_domain = false; |
| 145 | } | 140 | } |
| 146 | 141 | ||
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 597d76d38..dd4de2904 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include <boost/intrusive/list.hpp> | 12 | #include <boost/intrusive/list.hpp> |
| 13 | 13 | ||
| 14 | #include "common/threadsafe_queue.h" | 14 | #include "common/threadsafe_queue.h" |
| 15 | #include "core/hle/kernel/hle_ipc.h" | ||
| 15 | #include "core/hle/kernel/k_synchronization_object.h" | 16 | #include "core/hle/kernel/k_synchronization_object.h" |
| 16 | #include "core/hle/kernel/service_thread.h" | 17 | #include "core/hle/kernel/service_thread.h" |
| 17 | #include "core/hle/result.h" | 18 | #include "core/hle/result.h" |
| @@ -64,8 +65,8 @@ public: | |||
| 64 | * instead of the regular IPC machinery. (The regular IPC machinery is currently not | 65 | * instead of the regular IPC machinery. (The regular IPC machinery is currently not |
| 65 | * implemented.) | 66 | * implemented.) |
| 66 | */ | 67 | */ |
| 67 | void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { | 68 | void SetSessionHandler(SessionRequestHandlerPtr handler) { |
| 68 | hle_handler = std::move(hle_handler_); | 69 | manager->SetSessionHandler(std::move(handler)); |
| 69 | } | 70 | } |
| 70 | 71 | ||
| 71 | /** | 72 | /** |
| @@ -82,7 +83,7 @@ public: | |||
| 82 | 83 | ||
| 83 | /// Adds a new domain request handler to the collection of request handlers within | 84 | /// Adds a new domain request handler to the collection of request handlers within |
| 84 | /// this ServerSession instance. | 85 | /// this ServerSession instance. |
| 85 | void AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler); | 86 | void AppendDomainHandler(SessionRequestHandlerPtr handler); |
| 86 | 87 | ||
| 87 | /// Retrieves the total number of domain request handlers that have been | 88 | /// Retrieves the total number of domain request handlers that have been |
| 88 | /// appended to this ServerSession instance. | 89 | /// appended to this ServerSession instance. |
| @@ -90,12 +91,7 @@ public: | |||
| 90 | 91 | ||
| 91 | /// Returns true if the session has been converted to a domain, otherwise False | 92 | /// Returns true if the session has been converted to a domain, otherwise False |
| 92 | bool IsDomain() const { | 93 | bool IsDomain() const { |
| 93 | return !IsSession(); | 94 | return manager->IsDomain(); |
| 94 | } | ||
| 95 | |||
| 96 | /// Returns true if this session has not been converted to a domain, otherwise false. | ||
| 97 | bool IsSession() const { | ||
| 98 | return domain_request_handlers.empty(); | ||
| 99 | } | 95 | } |
| 100 | 96 | ||
| 101 | /// Converts the session to a domain at the end of the current command | 97 | /// Converts the session to a domain at the end of the current command |
| @@ -103,6 +99,21 @@ public: | |||
| 103 | convert_to_domain = true; | 99 | convert_to_domain = true; |
| 104 | } | 100 | } |
| 105 | 101 | ||
| 102 | /// Gets the session request manager, which forwards requests to the underlying service | ||
| 103 | std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() { | ||
| 104 | return manager; | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Gets the session request manager, which forwards requests to the underlying service | ||
| 108 | const std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() const { | ||
| 109 | return manager; | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Sets the session request manager, which forwards requests to the underlying service | ||
| 113 | void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) { | ||
| 114 | manager = std::move(manager_); | ||
| 115 | } | ||
| 116 | |||
| 106 | private: | 117 | private: |
| 107 | /// Queues a sync request from the emulated application. | 118 | /// Queues a sync request from the emulated application. |
| 108 | ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory); | 119 | ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory); |
| @@ -114,11 +125,8 @@ private: | |||
| 114 | /// object handle. | 125 | /// object handle. |
| 115 | ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context); | 126 | ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context); |
| 116 | 127 | ||
| 117 | /// This session's HLE request handler (applicable when not a domain) | 128 | /// This session's HLE request handlers |
| 118 | std::shared_ptr<SessionRequestHandler> hle_handler; | 129 | std::shared_ptr<SessionRequestManager> manager; |
| 119 | |||
| 120 | /// This is the list of domain request handlers (after conversion to a domain) | ||
| 121 | std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; | ||
| 122 | 130 | ||
| 123 | /// When set to True, converts the session to a domain at the end of the command | 131 | /// When set to True, converts the session to a domain at the end of the command |
| 124 | bool convert_to_domain{}; | 132 | bool convert_to_domain{}; |
diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp index b7ce27a0b..025b8b555 100644 --- a/src/core/hle/kernel/k_session.cpp +++ b/src/core/hle/kernel/k_session.cpp | |||
| @@ -78,7 +78,7 @@ void KSession::OnClientClosed() { | |||
| 78 | void KSession::PostDestroy(uintptr_t arg) { | 78 | void KSession::PostDestroy(uintptr_t arg) { |
| 79 | // Release the session count resource the owner process holds. | 79 | // Release the session count resource the owner process holds. |
| 80 | KProcess* owner = reinterpret_cast<KProcess*>(arg); | 80 | KProcess* owner = reinterpret_cast<KProcess*>(arg); |
| 81 | // owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1); | 81 | owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1); |
| 82 | owner->Close(); | 82 | owner->Close(); |
| 83 | } | 83 | } |
| 84 | 84 | ||
diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h index 16901e19c..a981fd1f6 100644 --- a/src/core/hle/kernel/k_session.h +++ b/src/core/hle/kernel/k_session.h | |||
| @@ -66,6 +66,10 @@ public: | |||
| 66 | return port; | 66 | return port; |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | KClientPort* GetParent() { | ||
| 70 | return port; | ||
| 71 | } | ||
| 72 | |||
| 69 | private: | 73 | private: |
| 70 | enum class State : u8 { | 74 | enum class State : u8 { |
| 71 | Invalid = 0, | 75 | Invalid = 0, |
diff --git a/src/core/hle/kernel/k_slab_heap.h b/src/core/hle/kernel/k_slab_heap.h index 5ce9a1d7c..81d472a3e 100644 --- a/src/core/hle/kernel/k_slab_heap.h +++ b/src/core/hle/kernel/k_slab_heap.h | |||
| @@ -4,165 +4,33 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <atomic> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | |||
| 12 | namespace Kernel { | 7 | namespace Kernel { |
| 13 | 8 | ||
| 14 | namespace impl { | 9 | class KernelCore; |
| 15 | |||
| 16 | class KSlabHeapImpl final : NonCopyable { | ||
| 17 | public: | ||
| 18 | struct Node { | ||
| 19 | Node* next{}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | constexpr KSlabHeapImpl() = default; | ||
| 23 | |||
| 24 | void Initialize(std::size_t size) { | ||
| 25 | ASSERT(head == nullptr); | ||
| 26 | obj_size = size; | ||
| 27 | } | ||
| 28 | |||
| 29 | constexpr std::size_t GetObjectSize() const { | ||
| 30 | return obj_size; | ||
| 31 | } | ||
| 32 | |||
| 33 | Node* GetHead() const { | ||
| 34 | return head; | ||
| 35 | } | ||
| 36 | |||
| 37 | void* Allocate() { | ||
| 38 | Node* ret = head.load(); | ||
| 39 | |||
| 40 | do { | ||
| 41 | if (ret == nullptr) { | ||
| 42 | break; | ||
| 43 | } | ||
| 44 | } while (!head.compare_exchange_weak(ret, ret->next)); | ||
| 45 | |||
| 46 | return ret; | ||
| 47 | } | ||
| 48 | |||
| 49 | void Free(void* obj) { | ||
| 50 | Node* node = static_cast<Node*>(obj); | ||
| 51 | |||
| 52 | Node* cur_head = head.load(); | ||
| 53 | do { | ||
| 54 | node->next = cur_head; | ||
| 55 | } while (!head.compare_exchange_weak(cur_head, node)); | ||
| 56 | } | ||
| 57 | |||
| 58 | private: | ||
| 59 | std::atomic<Node*> head{}; | ||
| 60 | std::size_t obj_size{}; | ||
| 61 | }; | ||
| 62 | |||
| 63 | } // namespace impl | ||
| 64 | |||
| 65 | class KSlabHeapBase : NonCopyable { | ||
| 66 | public: | ||
| 67 | constexpr KSlabHeapBase() = default; | ||
| 68 | |||
| 69 | constexpr bool Contains(uintptr_t addr) const { | ||
| 70 | return start <= addr && addr < end; | ||
| 71 | } | ||
| 72 | |||
| 73 | constexpr std::size_t GetSlabHeapSize() const { | ||
| 74 | return (end - start) / GetObjectSize(); | ||
| 75 | } | ||
| 76 | |||
| 77 | constexpr std::size_t GetObjectSize() const { | ||
| 78 | return impl.GetObjectSize(); | ||
| 79 | } | ||
| 80 | 10 | ||
| 81 | constexpr uintptr_t GetSlabHeapAddress() const { | 11 | /// This is a placeholder class to manage slab heaps for kernel objects. For now, we just allocate |
| 82 | return start; | 12 | /// these with new/delete, but this can be re-implemented later to allocate these in emulated |
| 83 | } | 13 | /// memory. |
| 84 | |||
| 85 | std::size_t GetObjectIndexImpl(const void* obj) const { | ||
| 86 | return (reinterpret_cast<uintptr_t>(obj) - start) / GetObjectSize(); | ||
| 87 | } | ||
| 88 | |||
| 89 | std::size_t GetPeakIndex() const { | ||
| 90 | return GetObjectIndexImpl(reinterpret_cast<const void*>(peak)); | ||
| 91 | } | ||
| 92 | |||
| 93 | void* AllocateImpl() { | ||
| 94 | return impl.Allocate(); | ||
| 95 | } | ||
| 96 | |||
| 97 | void FreeImpl(void* obj) { | ||
| 98 | // Don't allow freeing an object that wasn't allocated from this heap | ||
| 99 | ASSERT(Contains(reinterpret_cast<uintptr_t>(obj))); | ||
| 100 | |||
| 101 | impl.Free(obj); | ||
| 102 | } | ||
| 103 | |||
| 104 | void InitializeImpl(std::size_t obj_size, void* memory, std::size_t memory_size) { | ||
| 105 | // Ensure we don't initialize a slab using null memory | ||
| 106 | ASSERT(memory != nullptr); | ||
| 107 | |||
| 108 | // Initialize the base allocator | ||
| 109 | impl.Initialize(obj_size); | ||
| 110 | |||
| 111 | // Set our tracking variables | ||
| 112 | const std::size_t num_obj = (memory_size / obj_size); | ||
| 113 | start = reinterpret_cast<uintptr_t>(memory); | ||
| 114 | end = start + num_obj * obj_size; | ||
| 115 | peak = start; | ||
| 116 | |||
| 117 | // Free the objects | ||
| 118 | u8* cur = reinterpret_cast<u8*>(end); | ||
| 119 | |||
| 120 | for (std::size_t i{}; i < num_obj; i++) { | ||
| 121 | cur -= obj_size; | ||
| 122 | impl.Free(cur); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | private: | ||
| 127 | using Impl = impl::KSlabHeapImpl; | ||
| 128 | |||
| 129 | Impl impl; | ||
| 130 | uintptr_t peak{}; | ||
| 131 | uintptr_t start{}; | ||
| 132 | uintptr_t end{}; | ||
| 133 | }; | ||
| 134 | 14 | ||
| 135 | template <typename T> | 15 | template <typename T> |
| 136 | class KSlabHeap final : public KSlabHeapBase { | 16 | class KSlabHeap final : NonCopyable { |
| 137 | public: | 17 | public: |
| 138 | constexpr KSlabHeap() : KSlabHeapBase() {} | 18 | KSlabHeap() = default; |
| 139 | 19 | ||
| 140 | void Initialize(void* memory, std::size_t memory_size) { | 20 | void Initialize([[maybe_unused]] void* memory, [[maybe_unused]] std::size_t memory_size) { |
| 141 | InitializeImpl(sizeof(T), memory, memory_size); | 21 | // Placeholder that should initialize the backing slab heap implementation. |
| 142 | } | 22 | } |
| 143 | 23 | ||
| 144 | T* Allocate() { | 24 | T* Allocate() { |
| 145 | T* obj = static_cast<T*>(AllocateImpl()); | 25 | return new T(); |
| 146 | if (obj != nullptr) { | ||
| 147 | new (obj) T(); | ||
| 148 | } | ||
| 149 | return obj; | ||
| 150 | } | 26 | } |
| 151 | 27 | ||
| 152 | T* AllocateWithKernel(KernelCore& kernel) { | 28 | T* AllocateWithKernel(KernelCore& kernel) { |
| 153 | T* obj = static_cast<T*>(AllocateImpl()); | 29 | return new T(kernel); |
| 154 | if (obj != nullptr) { | ||
| 155 | new (obj) T(kernel); | ||
| 156 | } | ||
| 157 | return obj; | ||
| 158 | } | 30 | } |
| 159 | 31 | ||
| 160 | void Free(T* obj) { | 32 | void Free(T* obj) { |
| 161 | FreeImpl(obj); | 33 | delete obj; |
| 162 | } | ||
| 163 | |||
| 164 | constexpr std::size_t GetObjectIndex(const T* obj) const { | ||
| 165 | return GetObjectIndexImpl(obj); | ||
| 166 | } | 34 | } |
| 167 | }; | 35 | }; |
| 168 | 36 | ||
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h index 838fd2b18..c2d0f1eaf 100644 --- a/src/core/hle/kernel/k_transfer_memory.h +++ b/src/core/hle/kernel/k_transfer_memory.h | |||
| @@ -52,7 +52,7 @@ public: | |||
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | size_t GetSize() const { | 54 | size_t GetSize() const { |
| 55 | return is_initialized ? size * PageSize : 0; | 55 | return is_initialized ? size : 0; |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | private: | 58 | private: |
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp index fcb8b1ea5..b2ceeceb3 100644 --- a/src/core/hle/kernel/process_capability.cpp +++ b/src/core/hle/kernel/process_capability.cpp | |||
| @@ -22,6 +22,7 @@ enum : u32 { | |||
| 22 | CapabilityOffset_Syscall = 4, | 22 | CapabilityOffset_Syscall = 4, |
| 23 | CapabilityOffset_MapPhysical = 6, | 23 | CapabilityOffset_MapPhysical = 6, |
| 24 | CapabilityOffset_MapIO = 7, | 24 | CapabilityOffset_MapIO = 7, |
| 25 | CapabilityOffset_MapRegion = 10, | ||
| 25 | CapabilityOffset_Interrupt = 11, | 26 | CapabilityOffset_Interrupt = 11, |
| 26 | CapabilityOffset_ProgramType = 13, | 27 | CapabilityOffset_ProgramType = 13, |
| 27 | CapabilityOffset_KernelVersion = 14, | 28 | CapabilityOffset_KernelVersion = 14, |
| @@ -46,6 +47,7 @@ enum class CapabilityType : u32 { | |||
| 46 | Syscall = (1U << CapabilityOffset_Syscall) - 1, | 47 | Syscall = (1U << CapabilityOffset_Syscall) - 1, |
| 47 | MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1, | 48 | MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1, |
| 48 | MapIO = (1U << CapabilityOffset_MapIO) - 1, | 49 | MapIO = (1U << CapabilityOffset_MapIO) - 1, |
| 50 | MapRegion = (1U << CapabilityOffset_MapRegion) - 1, | ||
| 49 | Interrupt = (1U << CapabilityOffset_Interrupt) - 1, | 51 | Interrupt = (1U << CapabilityOffset_Interrupt) - 1, |
| 50 | ProgramType = (1U << CapabilityOffset_ProgramType) - 1, | 52 | ProgramType = (1U << CapabilityOffset_ProgramType) - 1, |
| 51 | KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1, | 53 | KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1, |
| @@ -187,6 +189,8 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s | |||
| 187 | return HandleSyscallFlags(set_svc_bits, flag); | 189 | return HandleSyscallFlags(set_svc_bits, flag); |
| 188 | case CapabilityType::MapIO: | 190 | case CapabilityType::MapIO: |
| 189 | return HandleMapIOFlags(flag, page_table); | 191 | return HandleMapIOFlags(flag, page_table); |
| 192 | case CapabilityType::MapRegion: | ||
| 193 | return HandleMapRegionFlags(flag, page_table); | ||
| 190 | case CapabilityType::Interrupt: | 194 | case CapabilityType::Interrupt: |
| 191 | return HandleInterruptFlags(flag); | 195 | return HandleInterruptFlags(flag); |
| 192 | case CapabilityType::ProgramType: | 196 | case CapabilityType::ProgramType: |
| @@ -298,6 +302,11 @@ ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, KPageTable& page_tab | |||
| 298 | return RESULT_SUCCESS; | 302 | return RESULT_SUCCESS; |
| 299 | } | 303 | } |
| 300 | 304 | ||
| 305 | ResultCode ProcessCapabilities::HandleMapRegionFlags(u32 flags, KPageTable& page_table) { | ||
| 306 | // TODO(Lioncache): Implement once the memory manager can handle this. | ||
| 307 | return RESULT_SUCCESS; | ||
| 308 | } | ||
| 309 | |||
| 301 | ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) { | 310 | ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) { |
| 302 | constexpr u32 interrupt_ignore_value = 0x3FF; | 311 | constexpr u32 interrupt_ignore_value = 0x3FF; |
| 303 | const u32 interrupt0 = (flags >> 12) & 0x3FF; | 312 | const u32 interrupt0 = (flags >> 12) & 0x3FF; |
diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h index b7a9b2e45..2a7bf5505 100644 --- a/src/core/hle/kernel/process_capability.h +++ b/src/core/hle/kernel/process_capability.h | |||
| @@ -231,6 +231,9 @@ private: | |||
| 231 | /// Handles flags related to mapping IO pages. | 231 | /// Handles flags related to mapping IO pages. |
| 232 | ResultCode HandleMapIOFlags(u32 flags, KPageTable& page_table); | 232 | ResultCode HandleMapIOFlags(u32 flags, KPageTable& page_table); |
| 233 | 233 | ||
| 234 | /// Handles flags related to mapping physical memory regions. | ||
| 235 | ResultCode HandleMapRegionFlags(u32 flags, KPageTable& page_table); | ||
| 236 | |||
| 234 | /// Handles flags related to the interrupt capability flags. | 237 | /// Handles flags related to the interrupt capability flags. |
| 235 | ResultCode HandleInterruptFlags(u32 flags); | 238 | ResultCode HandleInterruptFlags(u32 flags); |
| 236 | 239 | ||
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp index 04be8a502..2ae80beca 100644 --- a/src/core/hle/kernel/service_thread.cpp +++ b/src/core/hle/kernel/service_thread.cpp | |||
| @@ -74,21 +74,17 @@ void ServiceThread::Impl::QueueSyncRequest(KSession& session, | |||
| 74 | { | 74 | { |
| 75 | std::unique_lock lock{queue_mutex}; | 75 | std::unique_lock lock{queue_mutex}; |
| 76 | 76 | ||
| 77 | auto* server_session{&session.GetServerSession()}; | ||
| 78 | |||
| 77 | // Open a reference to the session to ensure it is not closes while the service request | 79 | // Open a reference to the session to ensure it is not closes while the service request |
| 78 | // completes asynchronously. | 80 | // completes asynchronously. |
| 79 | session.Open(); | 81 | server_session->Open(); |
| 80 | 82 | ||
| 81 | requests.emplace([session_ptr{&session}, context{std::move(context)}]() { | 83 | requests.emplace([server_session, context{std::move(context)}]() { |
| 82 | // Close the reference. | 84 | // Close the reference. |
| 83 | SCOPE_EXIT({ session_ptr->Close(); }); | 85 | SCOPE_EXIT({ server_session->Close(); }); |
| 84 | |||
| 85 | // If the session has been closed, we are done. | ||
| 86 | if (session_ptr->IsServerClosed()) { | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | 86 | ||
| 90 | // Complete the service request. | 87 | // Complete the service request. |
| 91 | KScopedAutoObject server_session{&session_ptr->GetServerSession()}; | ||
| 92 | server_session->CompleteSyncRequest(*context); | 88 | server_session->CompleteSyncRequest(*context); |
| 93 | }); | 89 | }); |
| 94 | } | 90 | } |
diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h index d0f7f084b..0c5995db0 100644 --- a/src/core/hle/kernel/slab_helpers.h +++ b/src/core/hle/kernel/slab_helpers.h | |||
| @@ -67,11 +67,11 @@ class KAutoObjectWithSlabHeapAndContainer : public Base { | |||
| 67 | 67 | ||
| 68 | private: | 68 | private: |
| 69 | static Derived* Allocate(KernelCore& kernel) { | 69 | static Derived* Allocate(KernelCore& kernel) { |
| 70 | return new Derived(kernel); | 70 | return kernel.SlabHeap<Derived>().AllocateWithKernel(kernel); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | static void Free(KernelCore& kernel, Derived* obj) { | 73 | static void Free(KernelCore& kernel, Derived* obj) { |
| 74 | delete obj; | 74 | kernel.SlabHeap<Derived>().Free(obj); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | public: | 77 | public: |
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 49c09a570..39cd1efc1 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp | |||
| @@ -4,9 +4,9 @@ | |||
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <array> | 6 | #include <array> |
| 7 | #include "common/common_paths.h" | ||
| 8 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 9 | #include "common/file_util.h" | 8 | #include "common/fs/file.h" |
| 9 | #include "common/fs/path_util.h" | ||
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/string_util.h" | 11 | #include "common/string_util.h" |
| 12 | #include "common/swap.h" | 12 | #include "common/swap.h" |
| @@ -41,9 +41,9 @@ constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; | |||
| 41 | // Thumbnails are hard coded to be at least this size | 41 | // Thumbnails are hard coded to be at least this size |
| 42 | constexpr std::size_t THUMBNAIL_SIZE = 0x24000; | 42 | constexpr std::size_t THUMBNAIL_SIZE = 0x24000; |
| 43 | 43 | ||
| 44 | static std::string GetImagePath(Common::UUID uuid) { | 44 | static std::filesystem::path GetImagePath(Common::UUID uuid) { |
| 45 | return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 45 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / |
| 46 | "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; | 46 | fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | static constexpr u32 SanitizeJPEGSize(std::size_t size) { | 49 | static constexpr u32 SanitizeJPEGSize(std::size_t size) { |
| @@ -328,7 +328,8 @@ protected: | |||
| 328 | IPC::ResponseBuilder rb{ctx, 3}; | 328 | IPC::ResponseBuilder rb{ctx, 3}; |
| 329 | rb.Push(RESULT_SUCCESS); | 329 | rb.Push(RESULT_SUCCESS); |
| 330 | 330 | ||
| 331 | const Common::FS::IOFile image(GetImagePath(user_id), "rb"); | 331 | const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, |
| 332 | Common::FS::FileType::BinaryFile); | ||
| 332 | if (!image.IsOpen()) { | 333 | if (!image.IsOpen()) { |
| 333 | LOG_WARNING(Service_ACC, | 334 | LOG_WARNING(Service_ACC, |
| 334 | "Failed to load user provided image! Falling back to built-in backup..."); | 335 | "Failed to load user provided image! Falling back to built-in backup..."); |
| @@ -339,7 +340,10 @@ protected: | |||
| 339 | 340 | ||
| 340 | const u32 size = SanitizeJPEGSize(image.GetSize()); | 341 | const u32 size = SanitizeJPEGSize(image.GetSize()); |
| 341 | std::vector<u8> buffer(size); | 342 | std::vector<u8> buffer(size); |
| 342 | image.ReadBytes(buffer.data(), buffer.size()); | 343 | |
| 344 | if (image.Read(buffer) != buffer.size()) { | ||
| 345 | LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image."); | ||
| 346 | } | ||
| 343 | 347 | ||
| 344 | ctx.WriteBuffer(buffer); | 348 | ctx.WriteBuffer(buffer); |
| 345 | rb.Push<u32>(size); | 349 | rb.Push<u32>(size); |
| @@ -350,7 +354,8 @@ protected: | |||
| 350 | IPC::ResponseBuilder rb{ctx, 3}; | 354 | IPC::ResponseBuilder rb{ctx, 3}; |
| 351 | rb.Push(RESULT_SUCCESS); | 355 | rb.Push(RESULT_SUCCESS); |
| 352 | 356 | ||
| 353 | const Common::FS::IOFile image(GetImagePath(user_id), "rb"); | 357 | const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, |
| 358 | Common::FS::FileType::BinaryFile); | ||
| 354 | 359 | ||
| 355 | if (!image.IsOpen()) { | 360 | if (!image.IsOpen()) { |
| 356 | LOG_WARNING(Service_ACC, | 361 | LOG_WARNING(Service_ACC, |
| @@ -415,10 +420,11 @@ protected: | |||
| 415 | ProfileData data; | 420 | ProfileData data; |
| 416 | std::memcpy(&data, user_data.data(), sizeof(ProfileData)); | 421 | std::memcpy(&data, user_data.data(), sizeof(ProfileData)); |
| 417 | 422 | ||
| 418 | Common::FS::IOFile image(GetImagePath(user_id), "wb"); | 423 | Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Write, |
| 424 | Common::FS::FileType::BinaryFile); | ||
| 419 | 425 | ||
| 420 | if (!image.IsOpen() || !image.Resize(image_data.size()) || | 426 | if (!image.IsOpen() || !image.SetSize(image_data.size()) || |
| 421 | image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || | 427 | image.Write(image_data) != image_data.size() || |
| 422 | !profile_manager.SetProfileBaseAndData(user_id, base, data)) { | 428 | !profile_manager.SetProfileBaseAndData(user_id, base, data)) { |
| 423 | LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); | 429 | LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); |
| 424 | IPC::ResponseBuilder rb{ctx, 2}; | 430 | IPC::ResponseBuilder rb{ctx, 2}; |
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index de83d82a4..77510489c 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp | |||
| @@ -7,7 +7,9 @@ | |||
| 7 | 7 | ||
| 8 | #include <fmt/format.h> | 8 | #include <fmt/format.h> |
| 9 | 9 | ||
| 10 | #include "common/file_util.h" | 10 | #include "common/fs/file.h" |
| 11 | #include "common/fs/fs.h" | ||
| 12 | #include "common/fs/path_util.h" | ||
| 11 | #include "common/settings.h" | 13 | #include "common/settings.h" |
| 12 | #include "core/hle/service/acc/profile_manager.h" | 14 | #include "core/hle/service/acc/profile_manager.h" |
| 13 | 15 | ||
| @@ -36,7 +38,7 @@ constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1)); | |||
| 36 | constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2)); | 38 | constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2)); |
| 37 | constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); | 39 | constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); |
| 38 | 40 | ||
| 39 | constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/"; | 41 | constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "system/save/8000000000000010/su/avators"; |
| 40 | 42 | ||
| 41 | ProfileManager::ProfileManager() { | 43 | ProfileManager::ProfileManager() { |
| 42 | ParseUserSaveFile(); | 44 | ParseUserSaveFile(); |
| @@ -325,8 +327,9 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& | |||
| 325 | } | 327 | } |
| 326 | 328 | ||
| 327 | void ProfileManager::ParseUserSaveFile() { | 329 | void ProfileManager::ParseUserSaveFile() { |
| 328 | const FS::IOFile save( | 330 | const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH / |
| 329 | FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb"); | 331 | "profiles.dat"); |
| 332 | const FS::IOFile save(save_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | ||
| 330 | 333 | ||
| 331 | if (!save.IsOpen()) { | 334 | if (!save.IsOpen()) { |
| 332 | LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " | 335 | LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " |
| @@ -335,7 +338,7 @@ void ProfileManager::ParseUserSaveFile() { | |||
| 335 | } | 338 | } |
| 336 | 339 | ||
| 337 | ProfileDataRaw data; | 340 | ProfileDataRaw data; |
| 338 | if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) { | 341 | if (!save.ReadObject(data)) { |
| 339 | LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user " | 342 | LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user " |
| 340 | "'yuzu' with random UUID."); | 343 | "'yuzu' with random UUID."); |
| 341 | return; | 344 | return; |
| @@ -372,31 +375,27 @@ void ProfileManager::WriteUserSaveFile() { | |||
| 372 | }; | 375 | }; |
| 373 | } | 376 | } |
| 374 | 377 | ||
| 375 | const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010"; | 378 | const auto raw_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / "system/save/8000000000000010"); |
| 376 | if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) { | 379 | if (FS::IsFile(raw_path) && !FS::RemoveFile(raw_path)) { |
| 377 | FS::Delete(raw_path); | 380 | return; |
| 378 | } | 381 | } |
| 379 | 382 | ||
| 380 | const auto path = | 383 | const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH / |
| 381 | FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; | 384 | "profiles.dat"); |
| 382 | 385 | ||
| 383 | if (!FS::CreateFullPath(path)) { | 386 | if (!FS::CreateParentDirs(save_path)) { |
| 384 | LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " | 387 | LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " |
| 385 | "nand/system/save/8000000000000010/su/avators to mitigate this " | 388 | "nand/system/save/8000000000000010/su/avators to mitigate this " |
| 386 | "issue."); | 389 | "issue."); |
| 387 | return; | 390 | return; |
| 388 | } | 391 | } |
| 389 | 392 | ||
| 390 | FS::IOFile save(path, "wb"); | 393 | FS::IOFile save(save_path, FS::FileAccessMode::Write, FS::FileType::BinaryFile); |
| 391 | 394 | ||
| 392 | if (!save.IsOpen()) { | 395 | if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) { |
| 393 | LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " | 396 | LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " |
| 394 | "made in current session will be saved."); | 397 | "made in current session will be saved."); |
| 395 | return; | ||
| 396 | } | 398 | } |
| 397 | |||
| 398 | save.Resize(sizeof(ProfileDataRaw)); | ||
| 399 | save.WriteBytes(&raw, sizeof(ProfileDataRaw)); | ||
| 400 | } | 399 | } |
| 401 | 400 | ||
| 402 | }; // namespace Service::Account | 401 | }; // namespace Service::Account |
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index e5f4a4485..3b28e829b 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 6 | #include "common/common_paths.h" | 6 | #include "common/fs/file.h" |
| 7 | #include "common/file_util.h" | 7 | #include "common/fs/fs.h" |
| 8 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 9 | #include "common/string_util.h" | 10 | #include "common/string_util.h" |
| 10 | #include "core/core.h" | 11 | #include "core/core.h" |
| @@ -135,14 +136,10 @@ void ExtractSharedFonts(Core::System& system) { | |||
| 135 | "FontNintendoExtended2.ttf", | 136 | "FontNintendoExtended2.ttf", |
| 136 | }; | 137 | }; |
| 137 | 138 | ||
| 138 | for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { | 139 | const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts"; |
| 139 | const auto fonts_dir = Common::FS::SanitizePath( | ||
| 140 | fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 141 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 142 | 140 | ||
| 143 | const auto font_file_path = | 141 | for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { |
| 144 | Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]), | 142 | const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i]; |
| 145 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 146 | 143 | ||
| 147 | if (Common::FS::Exists(font_file_path)) { | 144 | if (Common::FS::Exists(font_file_path)) { |
| 148 | continue; | 145 | continue; |
| @@ -197,8 +194,8 @@ void ExtractSharedFonts(Core::System& system) { | |||
| 197 | FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>( | 194 | FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>( |
| 198 | std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); | 195 | std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); |
| 199 | 196 | ||
| 200 | const auto temp_dir = | 197 | const auto temp_dir = system.GetFilesystem()->CreateDirectory( |
| 201 | system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite); | 198 | Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite); |
| 202 | 199 | ||
| 203 | const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); | 200 | const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); |
| 204 | 201 | ||
| @@ -312,13 +309,14 @@ void WebBrowser::Execute() { | |||
| 312 | } | 309 | } |
| 313 | 310 | ||
| 314 | void WebBrowser::ExtractOfflineRomFS() { | 311 | void WebBrowser::ExtractOfflineRomFS() { |
| 315 | LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir); | 312 | LOG_DEBUG(Service_AM, "Extracting RomFS to {}", |
| 313 | Common::FS::PathToUTF8String(offline_cache_dir)); | ||
| 316 | 314 | ||
| 317 | const auto extracted_romfs_dir = | 315 | const auto extracted_romfs_dir = |
| 318 | FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); | 316 | FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); |
| 319 | 317 | ||
| 320 | const auto temp_dir = | 318 | const auto temp_dir = system.GetFilesystem()->CreateDirectory( |
| 321 | system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite); | 319 | Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite); |
| 322 | 320 | ||
| 323 | FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); | 321 | FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); |
| 324 | } | 322 | } |
| @@ -397,15 +395,12 @@ void WebBrowser::InitializeOffline() { | |||
| 397 | "system_data", | 395 | "system_data", |
| 398 | }; | 396 | }; |
| 399 | 397 | ||
| 400 | offline_cache_dir = Common::FS::SanitizePath( | 398 | offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 401 | fmt::format("{}/offline_web_applet_{}/{:016X}", | 399 | fmt::format("offline_web_applet_{}/{:016X}", |
| 402 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), | 400 | RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id); |
| 403 | RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id), | ||
| 404 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 405 | 401 | ||
| 406 | offline_document = Common::FS::SanitizePath( | 402 | offline_document = Common::FS::ConcatPathSafe( |
| 407 | fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path), | 403 | offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path)); |
| 408 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 409 | } | 404 | } |
| 410 | 405 | ||
| 411 | void WebBrowser::InitializeShare() {} | 406 | void WebBrowser::InitializeShare() {} |
| @@ -429,8 +424,7 @@ void WebBrowser::ExecuteLogin() { | |||
| 429 | } | 424 | } |
| 430 | 425 | ||
| 431 | void WebBrowser::ExecuteOffline() { | 426 | void WebBrowser::ExecuteOffline() { |
| 432 | const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document), | 427 | const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document)); |
| 433 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 434 | 428 | ||
| 435 | if (!Common::FS::Exists(main_url)) { | 429 | if (!Common::FS::Exists(main_url)) { |
| 436 | offline_romfs = GetOfflineRomFS(system, title_id, nca_type); | 430 | offline_romfs = GetOfflineRomFS(system, title_id, nca_type); |
| @@ -444,10 +438,11 @@ void WebBrowser::ExecuteOffline() { | |||
| 444 | } | 438 | } |
| 445 | } | 439 | } |
| 446 | 440 | ||
| 447 | LOG_INFO(Service_AM, "Opening offline document at {}", offline_document); | 441 | LOG_INFO(Service_AM, "Opening offline document at {}", |
| 442 | Common::FS::PathToUTF8String(offline_document)); | ||
| 448 | 443 | ||
| 449 | frontend.OpenLocalWebPage( | 444 | frontend.OpenLocalWebPage( |
| 450 | offline_document, [this] { ExtractOfflineRomFS(); }, | 445 | Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); }, |
| 451 | [this](WebExitReason exit_reason, std::string last_url) { | 446 | [this](WebExitReason exit_reason, std::string last_url) { |
| 452 | WebBrowserExit(exit_reason, last_url); | 447 | WebBrowserExit(exit_reason, last_url); |
| 453 | }); | 448 | }); |
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 1e1812f36..cdeaf2c40 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <filesystem> | ||
| 7 | #include <optional> | 8 | #include <optional> |
| 8 | 9 | ||
| 9 | #include "common/common_funcs.h" | 10 | #include "common/common_funcs.h" |
| @@ -75,8 +76,8 @@ private: | |||
| 75 | 76 | ||
| 76 | u64 title_id{}; | 77 | u64 title_id{}; |
| 77 | FileSys::ContentRecordType nca_type{}; | 78 | FileSys::ContentRecordType nca_type{}; |
| 78 | std::string offline_cache_dir; | 79 | std::filesystem::path offline_cache_dir; |
| 79 | std::string offline_document; | 80 | std::filesystem::path offline_document; |
| 80 | FileSys::VirtualFile offline_romfs; | 81 | FileSys::VirtualFile offline_romfs; |
| 81 | 82 | ||
| 82 | std::string external_url; | 83 | std::string external_url; |
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index d6d2f52e5..3cc397604 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp | |||
| @@ -15,6 +15,9 @@ | |||
| 15 | #pragma GCC diagnostic pop | 15 | #pragma GCC diagnostic pop |
| 16 | #endif | 16 | #endif |
| 17 | 17 | ||
| 18 | #include "common/fs/file.h" | ||
| 19 | #include "common/fs/fs.h" | ||
| 20 | #include "common/fs/path_util.h" | ||
| 18 | #include "common/hex_util.h" | 21 | #include "common/hex_util.h" |
| 19 | #include "common/logging/backend.h" | 22 | #include "common/logging/backend.h" |
| 20 | #include "common/logging/log.h" | 23 | #include "common/logging/log.h" |
| @@ -96,14 +99,14 @@ constexpr u32 PORT = 443; | |||
| 96 | constexpr u32 TIMEOUT_SECONDS = 30; | 99 | constexpr u32 TIMEOUT_SECONDS = 30; |
| 97 | [[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB | 100 | [[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB |
| 98 | 101 | ||
| 99 | std::string GetBINFilePath(u64 title_id) { | 102 | std::filesystem::path GetBINFilePath(u64 title_id) { |
| 100 | return fmt::format("{}bcat/{:016X}/launchparam.bin", | 103 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / |
| 101 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); | 104 | fmt::format("{:016X}/launchparam.bin", title_id); |
| 102 | } | 105 | } |
| 103 | 106 | ||
| 104 | std::string GetZIPFilePath(u64 title_id) { | 107 | std::filesystem::path GetZIPFilePath(u64 title_id) { |
| 105 | return fmt::format("{}bcat/{:016X}/data.zip", | 108 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / |
| 106 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); | 109 | fmt::format("{:016X}/data.zip", title_id); |
| 107 | } | 110 | } |
| 108 | 111 | ||
| 109 | // If the error is something the user should know about (build ID mismatch, bad client version), | 112 | // If the error is something the user should know about (build ID mismatch, bad client version), |
| @@ -187,7 +190,7 @@ bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest, | |||
| 187 | 190 | ||
| 188 | class Boxcat::Client { | 191 | class Boxcat::Client { |
| 189 | public: | 192 | public: |
| 190 | Client(std::string path_, u64 title_id_, u64 build_id_) | 193 | Client(std::filesystem::path path_, u64 title_id_, u64 build_id_) |
| 191 | : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {} | 194 | : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {} |
| 192 | 195 | ||
| 193 | DownloadResult DownloadDataZip() { | 196 | DownloadResult DownloadDataZip() { |
| @@ -217,10 +220,11 @@ private: | |||
| 217 | }; | 220 | }; |
| 218 | 221 | ||
| 219 | if (Common::FS::Exists(path)) { | 222 | if (Common::FS::Exists(path)) { |
| 220 | Common::FS::IOFile file{path, "rb"}; | 223 | Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read, |
| 224 | Common::FS::FileType::BinaryFile}; | ||
| 221 | if (file.IsOpen()) { | 225 | if (file.IsOpen()) { |
| 222 | std::vector<u8> bytes(file.GetSize()); | 226 | std::vector<u8> bytes(file.GetSize()); |
| 223 | file.ReadBytes(bytes.data(), bytes.size()); | 227 | void(file.Read(bytes)); |
| 224 | const auto digest = DigestFile(bytes); | 228 | const auto digest = DigestFile(bytes); |
| 225 | headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); | 229 | headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); |
| 226 | } | 230 | } |
| @@ -247,14 +251,23 @@ private: | |||
| 247 | return DownloadResult::InvalidContentType; | 251 | return DownloadResult::InvalidContentType; |
| 248 | } | 252 | } |
| 249 | 253 | ||
| 250 | Common::FS::CreateFullPath(path); | 254 | if (!Common::FS::CreateDirs(path)) { |
| 251 | Common::FS::IOFile file{path, "wb"}; | ||
| 252 | if (!file.IsOpen()) | ||
| 253 | return DownloadResult::GeneralFSError; | 255 | return DownloadResult::GeneralFSError; |
| 254 | if (!file.Resize(response->body.size())) | 256 | } |
| 257 | |||
| 258 | Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append, | ||
| 259 | Common::FS::FileType::BinaryFile}; | ||
| 260 | if (!file.IsOpen()) { | ||
| 261 | return DownloadResult::GeneralFSError; | ||
| 262 | } | ||
| 263 | |||
| 264 | if (!file.SetSize(response->body.size())) { | ||
| 255 | return DownloadResult::GeneralFSError; | 265 | return DownloadResult::GeneralFSError; |
| 256 | if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size()) | 266 | } |
| 267 | |||
| 268 | if (file.Write(response->body) != response->body.size()) { | ||
| 257 | return DownloadResult::GeneralFSError; | 269 | return DownloadResult::GeneralFSError; |
| 270 | } | ||
| 258 | 271 | ||
| 259 | return DownloadResult::Success; | 272 | return DownloadResult::Success; |
| 260 | } | 273 | } |
| @@ -267,7 +280,7 @@ private: | |||
| 267 | } | 280 | } |
| 268 | 281 | ||
| 269 | std::unique_ptr<httplib::SSLClient> client; | 282 | std::unique_ptr<httplib::SSLClient> client; |
| 270 | std::string path; | 283 | std::filesystem::path path; |
| 271 | u64 title_id; | 284 | u64 title_id; |
| 272 | u64 build_id; | 285 | u64 build_id; |
| 273 | }; | 286 | }; |
| @@ -291,7 +304,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe | |||
| 291 | return; | 304 | return; |
| 292 | } | 305 | } |
| 293 | 306 | ||
| 294 | const auto zip_path{GetZIPFilePath(title.title_id)}; | 307 | const auto zip_path = GetZIPFilePath(title.title_id); |
| 295 | Boxcat::Client client{zip_path, title.title_id, title.build_id}; | 308 | Boxcat::Client client{zip_path, title.title_id, title.build_id}; |
| 296 | 309 | ||
| 297 | progress.StartConnecting(); | 310 | progress.StartConnecting(); |
| @@ -301,7 +314,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe | |||
| 301 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | 314 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); |
| 302 | 315 | ||
| 303 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | 316 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { |
| 304 | Common::FS::Delete(zip_path); | 317 | void(Common::FS::RemoveFile(zip_path)); |
| 305 | } | 318 | } |
| 306 | 319 | ||
| 307 | HandleDownloadDisplayResult(applet_manager, res); | 320 | HandleDownloadDisplayResult(applet_manager, res); |
| @@ -311,11 +324,13 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe | |||
| 311 | 324 | ||
| 312 | progress.StartProcessingDataList(); | 325 | progress.StartProcessingDataList(); |
| 313 | 326 | ||
| 314 | Common::FS::IOFile zip{zip_path, "rb"}; | 327 | Common::FS::IOFile zip{zip_path, Common::FS::FileAccessMode::Read, |
| 328 | Common::FS::FileType::BinaryFile}; | ||
| 315 | const auto size = zip.GetSize(); | 329 | const auto size = zip.GetSize(); |
| 316 | std::vector<u8> bytes(size); | 330 | std::vector<u8> bytes(size); |
| 317 | if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { | 331 | if (!zip.IsOpen() || size == 0 || zip.Read(bytes) != bytes.size()) { |
| 318 | LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); | 332 | LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", |
| 333 | Common::FS::PathToUTF8String(zip_path)); | ||
| 319 | progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); | 334 | progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); |
| 320 | return; | 335 | return; |
| 321 | } | 336 | } |
| @@ -419,19 +434,19 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { | |||
| 419 | } | 434 | } |
| 420 | 435 | ||
| 421 | std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { | 436 | std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { |
| 422 | const auto path{GetBINFilePath(title.title_id)}; | 437 | const auto bin_file_path = GetBINFilePath(title.title_id); |
| 423 | 438 | ||
| 424 | if (Settings::values.bcat_boxcat_local) { | 439 | if (Settings::values.bcat_boxcat_local) { |
| 425 | LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); | 440 | LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); |
| 426 | } else { | 441 | } else { |
| 427 | Client launch_client{path, title.title_id, title.build_id}; | 442 | Client launch_client{bin_file_path, title.title_id, title.build_id}; |
| 428 | 443 | ||
| 429 | const auto res = launch_client.DownloadLaunchParam(); | 444 | const auto res = launch_client.DownloadLaunchParam(); |
| 430 | if (res != DownloadResult::Success) { | 445 | if (res != DownloadResult::Success) { |
| 431 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | 446 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); |
| 432 | 447 | ||
| 433 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | 448 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { |
| 434 | Common::FS::Delete(path); | 449 | void(Common::FS::RemoveFile(bin_file_path)); |
| 435 | } | 450 | } |
| 436 | 451 | ||
| 437 | HandleDownloadDisplayResult(applet_manager, res); | 452 | HandleDownloadDisplayResult(applet_manager, res); |
| @@ -439,12 +454,13 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) | |||
| 439 | } | 454 | } |
| 440 | } | 455 | } |
| 441 | 456 | ||
| 442 | Common::FS::IOFile bin{path, "rb"}; | 457 | Common::FS::IOFile bin{bin_file_path, Common::FS::FileAccessMode::Read, |
| 458 | Common::FS::FileType::BinaryFile}; | ||
| 443 | const auto size = bin.GetSize(); | 459 | const auto size = bin.GetSize(); |
| 444 | std::vector<u8> bytes(size); | 460 | std::vector<u8> bytes(size); |
| 445 | if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { | 461 | if (!bin.IsOpen() || size == 0 || bin.Read(bytes) != bytes.size()) { |
| 446 | LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", | 462 | LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", |
| 447 | path); | 463 | Common::FS::PathToUTF8String(bin_file_path)); |
| 448 | return std::nullopt; | 464 | return std::nullopt; |
| 449 | } | 465 | } |
| 450 | 466 | ||
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 432abde76..b7666e95a 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp | |||
| @@ -6,7 +6,6 @@ | |||
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include <ctime> | 7 | #include <ctime> |
| 8 | #include <fmt/chrono.h> | 8 | #include <fmt/chrono.h> |
| 9 | #include "common/file_util.h" | ||
| 10 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 11 | #include "common/scm_rev.h" | 10 | #include "common/scm_rev.h" |
| 12 | #include "common/swap.h" | 11 | #include "common/swap.h" |
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 67baaee9b..78664439d 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include <utility> | 5 | #include <utility> |
| 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/settings.h" | 9 | #include "common/settings.h" |
| 10 | #include "core/core.h" | 10 | #include "core/core.h" |
| 11 | #include "core/file_sys/bis_factory.h" | 11 | #include "core/file_sys/bis_factory.h" |
| @@ -728,14 +728,17 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove | |||
| 728 | sdmc_factory = nullptr; | 728 | sdmc_factory = nullptr; |
| 729 | } | 729 | } |
| 730 | 730 | ||
| 731 | auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir), | 731 | using YuzuPath = Common::FS::YuzuPath; |
| 732 | FileSys::Mode::ReadWrite); | 732 | const auto rw_mode = FileSys::Mode::ReadWrite; |
| 733 | auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), | 733 | |
| 734 | FileSys::Mode::ReadWrite); | 734 | auto nand_directory = |
| 735 | auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), | 735 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); |
| 736 | FileSys::Mode::ReadWrite); | 736 | auto sd_directory = |
| 737 | auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), | 737 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode); |
| 738 | FileSys::Mode::ReadWrite); | 738 | auto load_directory = |
| 739 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); | ||
| 740 | auto dump_directory = | ||
| 741 | vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); | ||
| 739 | 742 | ||
| 740 | if (bis_factory == nullptr) { | 743 | if (bis_factory == nullptr) { |
| 741 | bis_factory = | 744 | bis_factory = |
diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp index 70350a2a3..114aff31c 100644 --- a/src/core/hle/service/mii/manager.cpp +++ b/src/core/hle/service/mii/manager.cpp | |||
| @@ -6,7 +6,6 @@ | |||
| 6 | #include <random> | 6 | #include <random> |
| 7 | 7 | ||
| 8 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 9 | #include "common/file_util.h" | ||
| 10 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 11 | #include "common/string_util.h" | 10 | #include "common/string_util.h" |
| 12 | 11 | ||
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index e14acce58..90ba5c752 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp | |||
| @@ -7,9 +7,7 @@ | |||
| 7 | #include <vector> | 7 | #include <vector> |
| 8 | 8 | ||
| 9 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 10 | #include "common/common_paths.h" | ||
| 11 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 12 | #include "common/file_util.h" | ||
| 13 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 14 | #include "common/swap.h" | 12 | #include "common/swap.h" |
| 15 | #include "core/core.h" | 13 | #include "core/core.h" |
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index bbef04a29..2cc0da124 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp | |||
| @@ -52,7 +52,6 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3 | |||
| 52 | addr, offset, width, height, stride, static_cast<PixelFormat>(format), | 52 | addr, offset, width, height, stride, static_cast<PixelFormat>(format), |
| 53 | transform, crop_rect}; | 53 | transform, crop_rect}; |
| 54 | 54 | ||
| 55 | system.GetPerfStats().EndGameFrame(); | ||
| 56 | system.GetPerfStats().EndSystemFrame(); | 55 | system.GetPerfStats().EndSystemFrame(); |
| 57 | system.GPU().SwapBuffers(&framebuffer); | 56 | system.GPU().SwapBuffers(&framebuffer); |
| 58 | system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs()); | 57 | system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs()); |
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 2c9b2ce6d..fa61a5c7b 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -107,7 +107,7 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) | |||
| 107 | ASSERT(!port_installed); | 107 | ASSERT(!port_installed); |
| 108 | 108 | ||
| 109 | auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); | 109 | auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); |
| 110 | port->SetHleHandler(shared_from_this()); | 110 | port->SetSessionHandler(shared_from_this()); |
| 111 | port_installed = true; | 111 | port_installed = true; |
| 112 | } | 112 | } |
| 113 | 113 | ||
| @@ -118,7 +118,7 @@ Kernel::KClientPort& ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel | |||
| 118 | 118 | ||
| 119 | auto* port = Kernel::KPort::Create(kernel); | 119 | auto* port = Kernel::KPort::Create(kernel); |
| 120 | port->Initialize(max_sessions, false, service_name); | 120 | port->Initialize(max_sessions, false, service_name); |
| 121 | port->GetServerPort().SetHleHandler(shared_from_this()); | 121 | port->GetServerPort().SetSessionHandler(shared_from_this()); |
| 122 | 122 | ||
| 123 | port_installed = true; | 123 | port_installed = true; |
| 124 | 124 | ||
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index de530cbfb..147f12147 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp | |||
| @@ -4,8 +4,13 @@ | |||
| 4 | 4 | ||
| 5 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 6 | #include "common/logging/log.h" | 6 | #include "common/logging/log.h" |
| 7 | #include "core/core.h" | ||
| 7 | #include "core/hle/ipc_helpers.h" | 8 | #include "core/hle/ipc_helpers.h" |
| 9 | #include "core/hle/kernel/k_client_port.h" | ||
| 8 | #include "core/hle/kernel/k_client_session.h" | 10 | #include "core/hle/kernel/k_client_session.h" |
| 11 | #include "core/hle/kernel/k_port.h" | ||
| 12 | #include "core/hle/kernel/k_scoped_resource_reservation.h" | ||
| 13 | #include "core/hle/kernel/k_server_port.h" | ||
| 9 | #include "core/hle/kernel/k_server_session.h" | 14 | #include "core/hle/kernel/k_server_session.h" |
| 10 | #include "core/hle/kernel/k_session.h" | 15 | #include "core/hle/kernel/k_session.h" |
| 11 | #include "core/hle/service/sm/controller.h" | 16 | #include "core/hle/service/sm/controller.h" |
| @@ -13,7 +18,7 @@ | |||
| 13 | namespace Service::SM { | 18 | namespace Service::SM { |
| 14 | 19 | ||
| 15 | void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { | 20 | void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { |
| 16 | ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain"); | 21 | ASSERT_MSG(!ctx.Session()->IsDomain(), "Session is already a domain"); |
| 17 | LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); | 22 | LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); |
| 18 | ctx.Session()->ConvertToDomain(); | 23 | ctx.Session()->ConvertToDomain(); |
| 19 | 24 | ||
| @@ -29,16 +34,36 @@ void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { | |||
| 29 | 34 | ||
| 30 | LOG_DEBUG(Service, "called"); | 35 | LOG_DEBUG(Service, "called"); |
| 31 | 36 | ||
| 32 | auto session = ctx.Session()->GetParent(); | 37 | auto& kernel = system.Kernel(); |
| 38 | auto* session = ctx.Session()->GetParent(); | ||
| 39 | auto* port = session->GetParent()->GetParent(); | ||
| 33 | 40 | ||
| 34 | // Open a reference to the session to simulate a new one being created. | 41 | // Reserve a new session from the process resource limit. |
| 35 | session->Open(); | 42 | Kernel::KScopedResourceReservation session_reservation( |
| 36 | session->GetClientSession().Open(); | 43 | kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); |
| 37 | session->GetServerSession().Open(); | 44 | if (!session_reservation.Succeeded()) { |
| 45 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 46 | rb.Push(Kernel::ResultLimitReached); | ||
| 47 | } | ||
| 38 | 48 | ||
| 49 | // Create a new session. | ||
| 50 | auto* clone = Kernel::KSession::Create(kernel); | ||
| 51 | clone->Initialize(&port->GetClientPort(), session->GetName()); | ||
| 52 | |||
| 53 | // Commit the session reservation. | ||
| 54 | session_reservation.Commit(); | ||
| 55 | |||
| 56 | // Enqueue the session with the named port. | ||
| 57 | port->EnqueueSession(&clone->GetServerSession()); | ||
| 58 | |||
| 59 | // Set the session request manager. | ||
| 60 | clone->GetServerSession().SetSessionRequestManager( | ||
| 61 | session->GetServerSession().GetSessionRequestManager()); | ||
| 62 | |||
| 63 | // We succeeded. | ||
| 39 | IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; | 64 | IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; |
| 40 | rb.Push(RESULT_SUCCESS); | 65 | rb.Push(RESULT_SUCCESS); |
| 41 | rb.PushMoveObjects(session->GetClientSession()); | 66 | rb.PushMoveObjects(clone->GetClientSession()); |
| 42 | } | 67 | } |
| 43 | 68 | ||
| 44 | void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { | 69 | void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 8cc9aee8a..a9bc7da74 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp | |||
| @@ -150,31 +150,31 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext& | |||
| 150 | IPC::RequestParser rp{ctx}; | 150 | IPC::RequestParser rp{ctx}; |
| 151 | std::string name(PopServiceName(rp)); | 151 | std::string name(PopServiceName(rp)); |
| 152 | 152 | ||
| 153 | // Find the named port. | ||
| 153 | auto result = service_manager.GetServicePort(name); | 154 | auto result = service_manager.GetServicePort(name); |
| 154 | if (result.Failed()) { | 155 | if (result.Failed()) { |
| 155 | LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.Code().raw); | 156 | LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.Code().raw); |
| 156 | return result.Code(); | 157 | return result.Code(); |
| 157 | } | 158 | } |
| 158 | |||
| 159 | auto* port = result.Unwrap(); | 159 | auto* port = result.Unwrap(); |
| 160 | 160 | ||
| 161 | // Kernel::KScopedResourceReservation session_reservation( | 161 | // Reserve a new session from the process resource limit. |
| 162 | // kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); | 162 | Kernel::KScopedResourceReservation session_reservation( |
| 163 | // R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached); | 163 | kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); |
| 164 | R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached); | ||
| 164 | 165 | ||
| 166 | // Create a new session. | ||
| 165 | auto* session = Kernel::KSession::Create(kernel); | 167 | auto* session = Kernel::KSession::Create(kernel); |
| 166 | session->Initialize(&port->GetClientPort(), std::move(name)); | 168 | session->Initialize(&port->GetClientPort(), std::move(name)); |
| 167 | 169 | ||
| 168 | // Commit the session reservation. | 170 | // Commit the session reservation. |
| 169 | // session_reservation.Commit(); | 171 | session_reservation.Commit(); |
| 170 | 172 | ||
| 171 | if (port->GetServerPort().GetHLEHandler()) { | 173 | // Enqueue the session with the named port. |
| 172 | port->GetServerPort().GetHLEHandler()->ClientConnected(&session->GetServerSession()); | 174 | port->EnqueueSession(&session->GetServerSession()); |
| 173 | } else { | ||
| 174 | port->EnqueueSession(&session->GetServerSession()); | ||
| 175 | } | ||
| 176 | 175 | ||
| 177 | LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); | 176 | LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); |
| 177 | |||
| 178 | return MakeResult(&session->GetClientSession()); | 178 | return MakeResult(&session->GetClientSession()); |
| 179 | } | 179 | } |
| 180 | 180 | ||
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 60f0b3f8a..ea37f11d4 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h | |||
| @@ -73,7 +73,7 @@ public: | |||
| 73 | if (port == nullptr) { | 73 | if (port == nullptr) { |
| 74 | return nullptr; | 74 | return nullptr; |
| 75 | } | 75 | } |
| 76 | return std::static_pointer_cast<T>(port->GetServerPort().GetHLEHandler()); | 76 | return std::static_pointer_cast<T>(port->GetServerPort().GetSessionRequestHandler()); |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | void InvokeControlRequest(Kernel::HLERequestContext& context); | 79 | void InvokeControlRequest(Kernel::HLERequestContext& context); |
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 022885c1b..a19bb220a 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp | |||
| @@ -5,7 +5,6 @@ | |||
| 5 | #include <cinttypes> | 5 | #include <cinttypes> |
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include "common/common_funcs.h" | 7 | #include "common/common_funcs.h" |
| 8 | #include "common/file_util.h" | ||
| 9 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 10 | #include "core/core.h" | 9 | #include "core/core.h" |
| 11 | #include "core/file_sys/content_archive.h" | 10 | #include "core/file_sys/content_archive.h" |
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index c062a4259..3d9276f15 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp | |||
| @@ -7,7 +7,6 @@ | |||
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/file_util.h" | ||
| 11 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 12 | #include "core/hle/kernel/code_set.h" | 11 | #include "core/hle/kernel/code_set.h" |
| 13 | #include "core/hle/kernel/k_page_table.h" | 12 | #include "core/hle/kernel/k_page_table.h" |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index d4808fb5b..228dc6389 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | #include <ostream> | 7 | #include <ostream> |
| 8 | #include <string> | 8 | #include <string> |
| 9 | #include "common/concepts.h" | 9 | #include "common/concepts.h" |
| 10 | #include "common/file_util.h" | 10 | #include "common/fs/path_util.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/string_util.h" | 12 | #include "common/string_util.h" |
| 13 | #include "core/core.h" | 13 | #include "core/core.h" |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 418cbf61b..aa51b0daa 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -4,7 +4,6 @@ | |||
| 4 | 4 | ||
| 5 | #include <utility> | 5 | #include <utility> |
| 6 | 6 | ||
| 7 | #include "common/file_util.h" | ||
| 8 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 9 | #include "core/core.h" | 8 | #include "core/core.h" |
| 10 | #include "core/file_sys/content_archive.h" | 9 | #include "core/file_sys/content_archive.h" |
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index ef54fa574..618555202 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp | |||
| @@ -7,7 +7,6 @@ | |||
| 7 | 7 | ||
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/file_util.h" | ||
| 11 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 12 | #include "common/settings.h" | 11 | #include "common/settings.h" |
| 13 | #include "common/swap.h" | 12 | #include "common/swap.h" |
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index df59412cf..0f5cfda68 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -7,7 +7,6 @@ | |||
| 7 | #include <vector> | 7 | #include <vector> |
| 8 | 8 | ||
| 9 | #include "common/common_funcs.h" | 9 | #include "common/common_funcs.h" |
| 10 | #include "common/file_util.h" | ||
| 11 | #include "common/hex_util.h" | 10 | #include "common/hex_util.h" |
| 12 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 13 | #include "common/lz4_compression.h" | 12 | #include "common/lz4_compression.h" |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b4c56e1c1..bf2ef7816 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -82,22 +82,6 @@ struct Memory::Impl { | |||
| 82 | return nullptr; | 82 | return nullptr; |
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | u8* GetKernelBuffer(VAddr start_vaddr, size_t size) { | ||
| 86 | // TODO(bunnei): This is just a workaround until we have kernel memory layout mapped & | ||
| 87 | // managed. Until then, we use this to allocate and access kernel memory regions. | ||
| 88 | |||
| 89 | auto search = kernel_memory_regions.find(start_vaddr); | ||
| 90 | if (search != kernel_memory_regions.end()) { | ||
| 91 | return search->second.get(); | ||
| 92 | } | ||
| 93 | |||
| 94 | std::unique_ptr<u8[]> new_memory_region{new u8[size]}; | ||
| 95 | u8* raw_ptr = new_memory_region.get(); | ||
| 96 | kernel_memory_regions[start_vaddr] = std::move(new_memory_region); | ||
| 97 | |||
| 98 | return raw_ptr; | ||
| 99 | } | ||
| 100 | |||
| 101 | u8 Read8(const VAddr addr) { | 85 | u8 Read8(const VAddr addr) { |
| 102 | return Read<u8>(addr); | 86 | return Read<u8>(addr); |
| 103 | } | 87 | } |
| @@ -727,7 +711,6 @@ struct Memory::Impl { | |||
| 727 | } | 711 | } |
| 728 | 712 | ||
| 729 | Common::PageTable* current_page_table = nullptr; | 713 | Common::PageTable* current_page_table = nullptr; |
| 730 | std::unordered_map<VAddr, std::unique_ptr<u8[]>> kernel_memory_regions; | ||
| 731 | Core::System& system; | 714 | Core::System& system; |
| 732 | }; | 715 | }; |
| 733 | 716 | ||
| @@ -765,10 +748,6 @@ u8* Memory::GetPointer(VAddr vaddr) { | |||
| 765 | return impl->GetPointer(vaddr); | 748 | return impl->GetPointer(vaddr); |
| 766 | } | 749 | } |
| 767 | 750 | ||
| 768 | u8* Memory::GetKernelBuffer(VAddr start_vaddr, size_t size) { | ||
| 769 | return impl->GetKernelBuffer(start_vaddr, size); | ||
| 770 | } | ||
| 771 | |||
| 772 | const u8* Memory::GetPointer(VAddr vaddr) const { | 751 | const u8* Memory::GetPointer(VAddr vaddr) const { |
| 773 | return impl->GetPointer(vaddr); | 752 | return impl->GetPointer(vaddr); |
| 774 | } | 753 | } |
diff --git a/src/core/memory.h b/src/core/memory.h index 345fd870d..c91eeced9 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -121,15 +121,6 @@ public: | |||
| 121 | */ | 121 | */ |
| 122 | u8* GetPointer(VAddr vaddr); | 122 | u8* GetPointer(VAddr vaddr); |
| 123 | 123 | ||
| 124 | /** | ||
| 125 | * Gets a pointer to the start of a kernel heap allocated memory region. Will allocate one if it | ||
| 126 | * does not already exist. | ||
| 127 | * | ||
| 128 | * @param start_vaddr Start virtual address for the memory region. | ||
| 129 | * @param size Size of the memory region. | ||
| 130 | */ | ||
| 131 | u8* GetKernelBuffer(VAddr start_vaddr, size_t size); | ||
| 132 | |||
| 133 | template <typename T> | 124 | template <typename T> |
| 134 | T* GetPointer(VAddr vaddr) { | 125 | T* GetPointer(VAddr vaddr) { |
| 135 | return reinterpret_cast<T*>(GetPointer(vaddr)); | 126 | return reinterpret_cast<T*>(GetPointer(vaddr)); |
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index b185a3884..6635a1339 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp | |||
| @@ -11,7 +11,9 @@ | |||
| 11 | #include <thread> | 11 | #include <thread> |
| 12 | #include <fmt/chrono.h> | 12 | #include <fmt/chrono.h> |
| 13 | #include <fmt/format.h> | 13 | #include <fmt/format.h> |
| 14 | #include "common/file_util.h" | 14 | #include "common/fs/file.h" |
| 15 | #include "common/fs/fs.h" | ||
| 16 | #include "common/fs/path_util.h" | ||
| 15 | #include "common/math_util.h" | 17 | #include "common/math_util.h" |
| 16 | #include "common/settings.h" | 18 | #include "common/settings.h" |
| 17 | #include "core/perf_stats.h" | 19 | #include "core/perf_stats.h" |
| @@ -38,12 +40,17 @@ PerfStats::~PerfStats() { | |||
| 38 | std::ostringstream stream; | 40 | std::ostringstream stream; |
| 39 | std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, | 41 | std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, |
| 40 | std::ostream_iterator<double>(stream, "\n")); | 42 | std::ostream_iterator<double>(stream, "\n")); |
| 41 | const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir); | 43 | |
| 44 | const auto path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir); | ||
| 42 | // %F Date format expanded is "%Y-%m-%d" | 45 | // %F Date format expanded is "%Y-%m-%d" |
| 43 | const std::string filename = | 46 | const auto filename = fmt::format("{:%F-%H-%M}_{:016X}.csv", *std::localtime(&t), title_id); |
| 44 | fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); | 47 | const auto filepath = path / filename; |
| 45 | Common::FS::IOFile file(filename, "w"); | 48 | |
| 46 | file.WriteString(stream.str()); | 49 | if (Common::FS::CreateParentDir(filepath)) { |
| 50 | Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write, | ||
| 51 | Common::FS::FileType::TextFile); | ||
| 52 | void(file.WriteString(stream.str())); | ||
| 53 | } | ||
| 47 | } | 54 | } |
| 48 | 55 | ||
| 49 | void PerfStats::BeginSystemFrame() { | 56 | void PerfStats::BeginSystemFrame() { |
| @@ -69,9 +76,7 @@ void PerfStats::EndSystemFrame() { | |||
| 69 | } | 76 | } |
| 70 | 77 | ||
| 71 | void PerfStats::EndGameFrame() { | 78 | void PerfStats::EndGameFrame() { |
| 72 | std::lock_guard lock{object_mutex}; | 79 | game_frames.fetch_add(1, std::memory_order_relaxed); |
| 73 | |||
| 74 | game_frames += 1; | ||
| 75 | } | 80 | } |
| 76 | 81 | ||
| 77 | double PerfStats::GetMeanFrametime() const { | 82 | double PerfStats::GetMeanFrametime() const { |
| @@ -94,10 +99,11 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us | |||
| 94 | const auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); | 99 | const auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); |
| 95 | 100 | ||
| 96 | const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval; | 101 | const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval; |
| 97 | 102 | const auto current_frames = static_cast<double>(game_frames.load(std::memory_order_relaxed)); | |
| 103 | const auto current_fps = current_frames / interval; | ||
| 98 | const PerfStatsResults results{ | 104 | const PerfStatsResults results{ |
| 99 | .system_fps = static_cast<double>(system_frames) / interval, | 105 | .system_fps = static_cast<double>(system_frames) / interval, |
| 100 | .game_fps = static_cast<double>(game_frames) / interval, | 106 | .average_game_fps = (current_fps + previous_fps) / 2.0, |
| 101 | .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / | 107 | .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / |
| 102 | static_cast<double>(system_frames), | 108 | static_cast<double>(system_frames), |
| 103 | .emulation_speed = system_us_per_second.count() / 1'000'000.0, | 109 | .emulation_speed = system_us_per_second.count() / 1'000'000.0, |
| @@ -108,7 +114,8 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us | |||
| 108 | reset_point_system_us = current_system_time_us; | 114 | reset_point_system_us = current_system_time_us; |
| 109 | accumulated_frametime = Clock::duration::zero(); | 115 | accumulated_frametime = Clock::duration::zero(); |
| 110 | system_frames = 0; | 116 | system_frames = 0; |
| 111 | game_frames = 0; | 117 | game_frames.store(0, std::memory_order_relaxed); |
| 118 | previous_fps = current_fps; | ||
| 112 | 119 | ||
| 113 | return results; | 120 | return results; |
| 114 | } | 121 | } |
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index ae4698696..e5d603717 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <atomic> | ||
| 8 | #include <chrono> | 9 | #include <chrono> |
| 9 | #include <cstddef> | 10 | #include <cstddef> |
| 10 | #include <mutex> | 11 | #include <mutex> |
| @@ -15,8 +16,8 @@ namespace Core { | |||
| 15 | struct PerfStatsResults { | 16 | struct PerfStatsResults { |
| 16 | /// System FPS (LCD VBlanks) in Hz | 17 | /// System FPS (LCD VBlanks) in Hz |
| 17 | double system_fps; | 18 | double system_fps; |
| 18 | /// Game FPS (GSP frame submissions) in Hz | 19 | /// Average game FPS (GPU frame renders) in Hz |
| 19 | double game_fps; | 20 | double average_game_fps; |
| 20 | /// Walltime per system frame, in seconds, excluding any waits | 21 | /// Walltime per system frame, in seconds, excluding any waits |
| 21 | double frametime; | 22 | double frametime; |
| 22 | /// Ratio of walltime / emulated time elapsed | 23 | /// Ratio of walltime / emulated time elapsed |
| @@ -72,7 +73,7 @@ private: | |||
| 72 | /// Cumulative number of system frames (LCD VBlanks) presented since last reset | 73 | /// Cumulative number of system frames (LCD VBlanks) presented since last reset |
| 73 | u32 system_frames = 0; | 74 | u32 system_frames = 0; |
| 74 | /// Cumulative number of game frames (GSP frame submissions) since last reset | 75 | /// Cumulative number of game frames (GSP frame submissions) since last reset |
| 75 | u32 game_frames = 0; | 76 | std::atomic<u32> game_frames = 0; |
| 76 | 77 | ||
| 77 | /// Point when the previous system frame ended | 78 | /// Point when the previous system frame ended |
| 78 | Clock::time_point previous_frame_end = reset_point; | 79 | Clock::time_point previous_frame_end = reset_point; |
| @@ -80,6 +81,8 @@ private: | |||
| 80 | Clock::time_point frame_begin = reset_point; | 81 | Clock::time_point frame_begin = reset_point; |
| 81 | /// Total visible duration (including frame-limiting, etc.) of the previous system frame | 82 | /// Total visible duration (including frame-limiting, etc.) of the previous system frame |
| 82 | Clock::duration previous_frame_length = Clock::duration::zero(); | 83 | Clock::duration previous_frame_length = Clock::duration::zero(); |
| 84 | /// Previously computed fps | ||
| 85 | double previous_fps = 0; | ||
| 83 | }; | 86 | }; |
| 84 | 87 | ||
| 85 | class FrameLimiter { | 88 | class FrameLimiter { |
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index d1e807dd4..a9596fe4d 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -11,7 +11,9 @@ | |||
| 11 | #include <fmt/ostream.h> | 11 | #include <fmt/ostream.h> |
| 12 | #include <nlohmann/json.hpp> | 12 | #include <nlohmann/json.hpp> |
| 13 | 13 | ||
| 14 | #include "common/file_util.h" | 14 | #include "common/fs/file.h" |
| 15 | #include "common/fs/fs.h" | ||
| 16 | #include "common/fs/path_util.h" | ||
| 15 | #include "common/hex_util.h" | 17 | #include "common/hex_util.h" |
| 16 | #include "common/scm_rev.h" | 18 | #include "common/scm_rev.h" |
| 17 | #include "common/settings.h" | 19 | #include "common/settings.h" |
| @@ -26,10 +28,9 @@ | |||
| 26 | 28 | ||
| 27 | namespace { | 29 | namespace { |
| 28 | 30 | ||
| 29 | std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { | 31 | std::filesystem::path GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { |
| 30 | return fmt::format("{}{}/{:016X}_{}.json", | 32 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / type / |
| 31 | Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id, | 33 | fmt::format("{:016X}_{}.json", title_id, timestamp); |
| 32 | timestamp); | ||
| 33 | } | 34 | } |
| 34 | 35 | ||
| 35 | std::string GetTimestamp() { | 36 | std::string GetTimestamp() { |
| @@ -39,14 +40,16 @@ std::string GetTimestamp() { | |||
| 39 | 40 | ||
| 40 | using namespace nlohmann; | 41 | using namespace nlohmann; |
| 41 | 42 | ||
| 42 | void SaveToFile(json json, const std::string& filename) { | 43 | void SaveToFile(json json, const std::filesystem::path& filename) { |
| 43 | if (!Common::FS::CreateFullPath(filename)) { | 44 | if (!Common::FS::CreateParentDirs(filename)) { |
| 44 | LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); | 45 | LOG_ERROR(Core, "Failed to create path for '{}' to save report!", |
| 46 | Common::FS::PathToUTF8String(filename)); | ||
| 45 | return; | 47 | return; |
| 46 | } | 48 | } |
| 47 | 49 | ||
| 48 | std::ofstream file( | 50 | std::ofstream file; |
| 49 | Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault)); | 51 | Common::FS::OpenFileStream(file, filename, std::ios_base::out | std::ios_base::trunc); |
| 52 | |||
| 50 | file << std::setw(4) << json << std::endl; | 53 | file << std::setw(4) << json << std::endl; |
| 51 | } | 54 | } |
| 52 | 55 | ||
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 6dcff5400..ad1a9ffb4 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp | |||
| @@ -9,7 +9,9 @@ | |||
| 9 | 9 | ||
| 10 | #include "common/assert.h" | 10 | #include "common/assert.h" |
| 11 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 12 | #include "common/file_util.h" | 12 | #include "common/fs/file.h" |
| 13 | #include "common/fs/fs.h" | ||
| 14 | #include "common/fs/path_util.h" | ||
| 13 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 14 | 16 | ||
| 15 | #include "common/settings.h" | 17 | #include "common/settings.h" |
| @@ -72,31 +74,41 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) { | |||
| 72 | 74 | ||
| 73 | u64 GetTelemetryId() { | 75 | u64 GetTelemetryId() { |
| 74 | u64 telemetry_id{}; | 76 | u64 telemetry_id{}; |
| 75 | const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + | 77 | const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; |
| 76 | "telemetry_id"}; | ||
| 77 | 78 | ||
| 78 | bool generate_new_id = !Common::FS::Exists(filename); | 79 | bool generate_new_id = !Common::FS::Exists(filename); |
| 80 | |||
| 79 | if (!generate_new_id) { | 81 | if (!generate_new_id) { |
| 80 | Common::FS::IOFile file(filename, "rb"); | 82 | Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read, |
| 83 | Common::FS::FileType::BinaryFile}; | ||
| 84 | |||
| 81 | if (!file.IsOpen()) { | 85 | if (!file.IsOpen()) { |
| 82 | LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); | 86 | LOG_ERROR(Core, "failed to open telemetry_id: {}", |
| 87 | Common::FS::PathToUTF8String(filename)); | ||
| 83 | return {}; | 88 | return {}; |
| 84 | } | 89 | } |
| 85 | file.ReadBytes(&telemetry_id, sizeof(u64)); | 90 | |
| 86 | if (telemetry_id == 0) { | 91 | if (!file.ReadObject(telemetry_id) || telemetry_id == 0) { |
| 87 | LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); | 92 | LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); |
| 88 | generate_new_id = true; | 93 | generate_new_id = true; |
| 89 | } | 94 | } |
| 90 | } | 95 | } |
| 91 | 96 | ||
| 92 | if (generate_new_id) { | 97 | if (generate_new_id) { |
| 93 | Common::FS::IOFile file(filename, "wb"); | 98 | Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, |
| 99 | Common::FS::FileType::BinaryFile}; | ||
| 100 | |||
| 94 | if (!file.IsOpen()) { | 101 | if (!file.IsOpen()) { |
| 95 | LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); | 102 | LOG_ERROR(Core, "failed to open telemetry_id: {}", |
| 103 | Common::FS::PathToUTF8String(filename)); | ||
| 96 | return {}; | 104 | return {}; |
| 97 | } | 105 | } |
| 106 | |||
| 98 | telemetry_id = GenerateTelemetryId(); | 107 | telemetry_id = GenerateTelemetryId(); |
| 99 | file.WriteBytes(&telemetry_id, sizeof(u64)); | 108 | |
| 109 | if (!file.WriteObject(telemetry_id)) { | ||
| 110 | LOG_ERROR(Core, "Failed to write telemetry_id to file."); | ||
| 111 | } | ||
| 100 | } | 112 | } |
| 101 | 113 | ||
| 102 | return telemetry_id; | 114 | return telemetry_id; |
| @@ -104,15 +116,20 @@ u64 GetTelemetryId() { | |||
| 104 | 116 | ||
| 105 | u64 RegenerateTelemetryId() { | 117 | u64 RegenerateTelemetryId() { |
| 106 | const u64 new_telemetry_id{GenerateTelemetryId()}; | 118 | const u64 new_telemetry_id{GenerateTelemetryId()}; |
| 107 | const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + | 119 | const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; |
| 108 | "telemetry_id"}; | 120 | |
| 121 | Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, | ||
| 122 | Common::FS::FileType::BinaryFile}; | ||
| 109 | 123 | ||
| 110 | Common::FS::IOFile file(filename, "wb"); | ||
| 111 | if (!file.IsOpen()) { | 124 | if (!file.IsOpen()) { |
| 112 | LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); | 125 | LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); |
| 113 | return {}; | 126 | return {}; |
| 114 | } | 127 | } |
| 115 | file.WriteBytes(&new_telemetry_id, sizeof(u64)); | 128 | |
| 129 | if (!file.WriteObject(new_telemetry_id)) { | ||
| 130 | LOG_ERROR(Core, "Failed to write telemetry_id to file."); | ||
| 131 | } | ||
| 132 | |||
| 116 | return new_telemetry_id; | 133 | return new_telemetry_id; |
| 117 | } | 134 | } |
| 118 | 135 | ||
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 822d0b555..b9b584b2a 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp | |||
| @@ -323,7 +323,9 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { | |||
| 323 | return joystick->GetSDLJoystick() == sdl_joystick; | 323 | return joystick->GetSDLJoystick() == sdl_joystick; |
| 324 | }); | 324 | }); |
| 325 | 325 | ||
| 326 | (*joystick_it)->SetSDLJoystick(nullptr, nullptr); | 326 | if (joystick_it != joystick_guid_list.end()) { |
| 327 | (*joystick_it)->SetSDLJoystick(nullptr, nullptr); | ||
| 328 | } | ||
| 327 | } | 329 | } |
| 328 | 330 | ||
| 329 | void SDLState::HandleGameControllerEvent(const SDL_Event& event) { | 331 | void SDLState::HandleGameControllerEvent(const SDL_Event& event) { |
| @@ -1315,51 +1317,51 @@ public: | |||
| 1315 | void Start(const std::string& device_id) override { | 1317 | void Start(const std::string& device_id) override { |
| 1316 | SDLPoller::Start(device_id); | 1318 | SDLPoller::Start(device_id); |
| 1317 | // Reset stored axes | 1319 | // Reset stored axes |
| 1318 | analog_x_axis = -1; | 1320 | first_axis = -1; |
| 1319 | analog_y_axis = -1; | ||
| 1320 | } | 1321 | } |
| 1321 | 1322 | ||
| 1322 | Common::ParamPackage GetNextInput() override { | 1323 | Common::ParamPackage GetNextInput() override { |
| 1323 | SDL_Event event; | 1324 | SDL_Event event; |
| 1324 | while (state.event_queue.Pop(event)) { | 1325 | while (state.event_queue.Pop(event)) { |
| 1325 | // Filter out axis events that are below a threshold | 1326 | if (event.type != SDL_JOYAXISMOTION) { |
| 1326 | if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { | 1327 | // Check for a button press |
| 1327 | continue; | ||
| 1328 | } | ||
| 1329 | if (event.type == SDL_JOYAXISMOTION) { | ||
| 1330 | const auto axis = event.jaxis.axis; | ||
| 1331 | // In order to return a complete analog param, we need inputs for both axes. | ||
| 1332 | // First we take the x-axis (horizontal) input, then the y-axis (vertical) input. | ||
| 1333 | if (analog_x_axis == -1) { | ||
| 1334 | analog_x_axis = axis; | ||
| 1335 | } else if (analog_y_axis == -1 && analog_x_axis != axis) { | ||
| 1336 | analog_y_axis = axis; | ||
| 1337 | } | ||
| 1338 | } else { | ||
| 1339 | // If the press wasn't accepted as a joy axis, check for a button press | ||
| 1340 | auto button_press = button_poller.FromEvent(event); | 1328 | auto button_press = button_poller.FromEvent(event); |
| 1341 | if (button_press) { | 1329 | if (button_press) { |
| 1342 | return *button_press; | 1330 | return *button_press; |
| 1343 | } | 1331 | } |
| 1332 | continue; | ||
| 1333 | } | ||
| 1334 | const auto axis = event.jaxis.axis; | ||
| 1335 | |||
| 1336 | // Filter out axis events that are below a threshold | ||
| 1337 | if (std::abs(event.jaxis.value / 32767.0) < 0.5) { | ||
| 1338 | continue; | ||
| 1339 | } | ||
| 1340 | |||
| 1341 | // Filter out axis events that are the same | ||
| 1342 | if (first_axis == axis) { | ||
| 1343 | continue; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | // In order to return a complete analog param, we need inputs for both axes. | ||
| 1347 | // If the first axis isn't set we set the value then wait till next event | ||
| 1348 | if (first_axis == -1) { | ||
| 1349 | first_axis = axis; | ||
| 1350 | continue; | ||
| 1344 | } | 1351 | } |
| 1345 | } | ||
| 1346 | 1352 | ||
| 1347 | if (analog_x_axis != -1 && analog_y_axis != -1) { | ||
| 1348 | if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { | 1353 | if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { |
| 1349 | auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), | 1354 | auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), |
| 1350 | analog_x_axis, analog_y_axis); | 1355 | first_axis, axis); |
| 1351 | analog_x_axis = -1; | 1356 | first_axis = -1; |
| 1352 | analog_y_axis = -1; | ||
| 1353 | return params; | 1357 | return params; |
| 1354 | } | 1358 | } |
| 1355 | } | 1359 | } |
| 1356 | |||
| 1357 | return {}; | 1360 | return {}; |
| 1358 | } | 1361 | } |
| 1359 | 1362 | ||
| 1360 | private: | 1363 | private: |
| 1361 | int analog_x_axis = -1; | 1364 | int first_axis = -1; |
| 1362 | int analog_y_axis = -1; | ||
| 1363 | SDLButtonPoller button_poller; | 1365 | SDLButtonPoller button_poller; |
| 1364 | }; | 1366 | }; |
| 1365 | } // namespace Polling | 1367 | } // namespace Polling |
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 8a38a380d..bc1dfab3d 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp | |||
| @@ -86,6 +86,7 @@ private: | |||
| 86 | case Type::PadData: { | 86 | case Type::PadData: { |
| 87 | Response::PadData pad_data; | 87 | Response::PadData pad_data; |
| 88 | std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); | 88 | std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); |
| 89 | SanitizeMotion(pad_data); | ||
| 89 | callback.pad_data(std::move(pad_data)); | 90 | callback.pad_data(std::move(pad_data)); |
| 90 | break; | 91 | break; |
| 91 | } | 92 | } |
| @@ -114,6 +115,28 @@ private: | |||
| 114 | StartSend(timer.expiry()); | 115 | StartSend(timer.expiry()); |
| 115 | } | 116 | } |
| 116 | 117 | ||
| 118 | void SanitizeMotion(Response::PadData& data) { | ||
| 119 | // Zero out any non number value | ||
| 120 | if (!std::isnormal(data.gyro.pitch)) { | ||
| 121 | data.gyro.pitch = 0; | ||
| 122 | } | ||
| 123 | if (!std::isnormal(data.gyro.roll)) { | ||
| 124 | data.gyro.roll = 0; | ||
| 125 | } | ||
| 126 | if (!std::isnormal(data.gyro.yaw)) { | ||
| 127 | data.gyro.yaw = 0; | ||
| 128 | } | ||
| 129 | if (!std::isnormal(data.accel.x)) { | ||
| 130 | data.accel.x = 0; | ||
| 131 | } | ||
| 132 | if (!std::isnormal(data.accel.y)) { | ||
| 133 | data.accel.y = 0; | ||
| 134 | } | ||
| 135 | if (!std::isnormal(data.accel.z)) { | ||
| 136 | data.accel.z = 0; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 117 | SocketCallback callback; | 140 | SocketCallback callback; |
| 118 | boost::asio::io_service io_service; | 141 | boost::asio::io_service io_service; |
| 119 | boost::asio::basic_waitable_timer<clock> timer; | 142 | boost::asio::basic_waitable_timer<clock> timer; |
diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index b35459152..e0c66fa2e 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp | |||
| @@ -11,7 +11,6 @@ | |||
| 11 | #include <memory> | 11 | #include <memory> |
| 12 | #include <string> | 12 | #include <string> |
| 13 | 13 | ||
| 14 | #include "common/file_util.h" | ||
| 15 | #include "core/core.h" | 14 | #include "core/core.h" |
| 16 | #include "core/core_timing.h" | 15 | #include "core/core_timing.h" |
| 17 | 16 | ||
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 32dcbd693..de971041f 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -690,7 +690,10 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 | |||
| 690 | const VAddr cpu_addr = binding.cpu_addr; | 690 | const VAddr cpu_addr = binding.cpu_addr; |
| 691 | const u32 size = binding.size; | 691 | const u32 size = binding.size; |
| 692 | Buffer& buffer = slot_buffers[binding.buffer_id]; | 692 | Buffer& buffer = slot_buffers[binding.buffer_id]; |
| 693 | if (size <= uniform_buffer_skip_cache_size && !buffer.IsRegionGpuModified(cpu_addr, size)) { | 693 | const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && |
| 694 | size <= uniform_buffer_skip_cache_size && | ||
| 695 | !buffer.IsRegionGpuModified(cpu_addr, size); | ||
| 696 | if (use_fast_buffer) { | ||
| 694 | if constexpr (IS_OPENGL) { | 697 | if constexpr (IS_OPENGL) { |
| 695 | if (runtime.HasFastBufferSubData()) { | 698 | if (runtime.HasFastBufferSubData()) { |
| 696 | // Fast path for Nvidia | 699 | // Fast path for Nvidia |
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index a38024242..37f7b24e1 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "core/frontend/emu_window.h" | 13 | #include "core/frontend/emu_window.h" |
| 14 | #include "core/hardware_interrupt_manager.h" | 14 | #include "core/hardware_interrupt_manager.h" |
| 15 | #include "core/memory.h" | 15 | #include "core/memory.h" |
| 16 | #include "core/perf_stats.h" | ||
| 16 | #include "video_core/engines/fermi_2d.h" | 17 | #include "video_core/engines/fermi_2d.h" |
| 17 | #include "video_core/engines/kepler_compute.h" | 18 | #include "video_core/engines/kepler_compute.h" |
| 18 | #include "video_core/engines/kepler_memory.h" | 19 | #include "video_core/engines/kepler_memory.h" |
| @@ -191,6 +192,10 @@ u64 GPU::GetTicks() const { | |||
| 191 | return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den; | 192 | return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den; |
| 192 | } | 193 | } |
| 193 | 194 | ||
| 195 | void GPU::RendererFrameEndNotify() { | ||
| 196 | system.GetPerfStats().EndGameFrame(); | ||
| 197 | } | ||
| 198 | |||
| 194 | void GPU::FlushCommands() { | 199 | void GPU::FlushCommands() { |
| 195 | rasterizer->FlushCommands(); | 200 | rasterizer->FlushCommands(); |
| 196 | } | 201 | } |
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 8669e9940..29a867863 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h | |||
| @@ -247,6 +247,8 @@ public: | |||
| 247 | return use_nvdec; | 247 | return use_nvdec; |
| 248 | } | 248 | } |
| 249 | 249 | ||
| 250 | void RendererFrameEndNotify(); | ||
| 251 | |||
| 250 | enum class FenceOperation : u32 { | 252 | enum class FenceOperation : u32 { |
| 251 | Acquire = 0, | 253 | Acquire = 0, |
| 252 | Increment = 1, | 254 | Increment = 1, |
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index b113f54db..3f4532ca7 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp | |||
| @@ -241,7 +241,7 @@ Device::Device() { | |||
| 241 | has_variable_aoffi = TestVariableAoffi(); | 241 | has_variable_aoffi = TestVariableAoffi(); |
| 242 | has_component_indexing_bug = is_amd; | 242 | has_component_indexing_bug = is_amd; |
| 243 | has_precise_bug = TestPreciseBug(); | 243 | has_precise_bug = TestPreciseBug(); |
| 244 | has_broken_texture_view_formats = is_amd || is_intel; | 244 | has_broken_texture_view_formats = is_amd || (!is_linux && is_intel); |
| 245 | has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2; | 245 | has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2; |
| 246 | has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory; | 246 | has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory; |
| 247 | has_debugging_tool_attached = IsDebugToolAttached(extensions); | 247 | has_debugging_tool_attached = IsDebugToolAttached(extensions); |
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index dbcb751cb..0deb86517 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp | |||
| @@ -7,9 +7,10 @@ | |||
| 7 | #include <fmt/format.h> | 7 | #include <fmt/format.h> |
| 8 | 8 | ||
| 9 | #include "common/assert.h" | 9 | #include "common/assert.h" |
| 10 | #include "common/common_paths.h" | ||
| 11 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 12 | #include "common/file_util.h" | 11 | #include "common/fs/file.h" |
| 12 | #include "common/fs/fs.h" | ||
| 13 | #include "common/fs/path_util.h" | ||
| 13 | #include "common/logging/log.h" | 14 | #include "common/logging/log.h" |
| 14 | #include "common/scm_rev.h" | 15 | #include "common/scm_rev.h" |
| 15 | #include "common/settings.h" | 16 | #include "common/settings.h" |
| @@ -26,11 +27,7 @@ using Tegra::Engines::ShaderType; | |||
| 26 | using VideoCommon::Shader::BindlessSamplerMap; | 27 | using VideoCommon::Shader::BindlessSamplerMap; |
| 27 | using VideoCommon::Shader::BoundSamplerMap; | 28 | using VideoCommon::Shader::BoundSamplerMap; |
| 28 | using VideoCommon::Shader::KeyMap; | 29 | using VideoCommon::Shader::KeyMap; |
| 29 | |||
| 30 | namespace { | ||
| 31 | |||
| 32 | using VideoCommon::Shader::SeparateSamplerKey; | 30 | using VideoCommon::Shader::SeparateSamplerKey; |
| 33 | |||
| 34 | using ShaderCacheVersionHash = std::array<u8, 64>; | 31 | using ShaderCacheVersionHash = std::array<u8, 64>; |
| 35 | 32 | ||
| 36 | struct ConstBufferKey { | 33 | struct ConstBufferKey { |
| @@ -58,6 +55,8 @@ struct BindlessSamplerEntry { | |||
| 58 | Tegra::Engines::SamplerDescriptor sampler; | 55 | Tegra::Engines::SamplerDescriptor sampler; |
| 59 | }; | 56 | }; |
| 60 | 57 | ||
| 58 | namespace { | ||
| 59 | |||
| 61 | constexpr u32 NativeVersion = 21; | 60 | constexpr u32 NativeVersion = 21; |
| 62 | 61 | ||
| 63 | ShaderCacheVersionHash GetShaderCacheVersionHash() { | 62 | ShaderCacheVersionHash GetShaderCacheVersionHash() { |
| @@ -74,22 +73,20 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default; | |||
| 74 | ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; | 73 | ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; |
| 75 | 74 | ||
| 76 | bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { | 75 | bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { |
| 77 | if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { | 76 | if (!file.ReadObject(type)) { |
| 78 | return false; | 77 | return false; |
| 79 | } | 78 | } |
| 80 | u32 code_size; | 79 | u32 code_size; |
| 81 | u32 code_size_b; | 80 | u32 code_size_b; |
| 82 | if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) || | 81 | if (!file.ReadObject(code_size) || !file.ReadObject(code_size_b)) { |
| 83 | file.ReadBytes(&code_size_b, sizeof(u32)) != sizeof(u32)) { | ||
| 84 | return false; | 82 | return false; |
| 85 | } | 83 | } |
| 86 | code.resize(code_size); | 84 | code.resize(code_size); |
| 87 | code_b.resize(code_size_b); | 85 | code_b.resize(code_size_b); |
| 88 | 86 | if (file.Read(code) != code_size) { | |
| 89 | if (file.ReadArray(code.data(), code_size) != code_size) { | ||
| 90 | return false; | 87 | return false; |
| 91 | } | 88 | } |
| 92 | if (HasProgramA() && file.ReadArray(code_b.data(), code_size_b) != code_size_b) { | 89 | if (HasProgramA() && file.Read(code_b) != code_size_b) { |
| 93 | return false; | 90 | return false; |
| 94 | } | 91 | } |
| 95 | 92 | ||
| @@ -99,13 +96,12 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { | |||
| 99 | u32 num_bound_samplers; | 96 | u32 num_bound_samplers; |
| 100 | u32 num_separate_samplers; | 97 | u32 num_separate_samplers; |
| 101 | u32 num_bindless_samplers; | 98 | u32 num_bindless_samplers; |
| 102 | if (file.ReadArray(&unique_identifier, 1) != 1 || file.ReadArray(&bound_buffer, 1) != 1 || | 99 | if (!file.ReadObject(unique_identifier) || !file.ReadObject(bound_buffer) || |
| 103 | file.ReadArray(&is_texture_handler_size_known, 1) != 1 || | 100 | !file.ReadObject(is_texture_handler_size_known) || |
| 104 | file.ReadArray(&texture_handler_size_value, 1) != 1 || | 101 | !file.ReadObject(texture_handler_size_value) || !file.ReadObject(graphics_info) || |
| 105 | file.ReadArray(&graphics_info, 1) != 1 || file.ReadArray(&compute_info, 1) != 1 || | 102 | !file.ReadObject(compute_info) || !file.ReadObject(num_keys) || |
| 106 | file.ReadArray(&num_keys, 1) != 1 || file.ReadArray(&num_bound_samplers, 1) != 1 || | 103 | !file.ReadObject(num_bound_samplers) || !file.ReadObject(num_separate_samplers) || |
| 107 | file.ReadArray(&num_separate_samplers, 1) != 1 || | 104 | !file.ReadObject(num_bindless_samplers)) { |
| 108 | file.ReadArray(&num_bindless_samplers, 1) != 1) { | ||
| 109 | return false; | 105 | return false; |
| 110 | } | 106 | } |
| 111 | if (is_texture_handler_size_known) { | 107 | if (is_texture_handler_size_known) { |
| @@ -116,13 +112,10 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { | |||
| 116 | std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers); | 112 | std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers); |
| 117 | std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers); | 113 | std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers); |
| 118 | std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers); | 114 | std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers); |
| 119 | if (file.ReadArray(flat_keys.data(), flat_keys.size()) != flat_keys.size() || | 115 | if (file.Read(flat_keys) != flat_keys.size() || |
| 120 | file.ReadArray(flat_bound_samplers.data(), flat_bound_samplers.size()) != | 116 | file.Read(flat_bound_samplers) != flat_bound_samplers.size() || |
| 121 | flat_bound_samplers.size() || | 117 | file.Read(flat_separate_samplers) != flat_separate_samplers.size() || |
| 122 | file.ReadArray(flat_separate_samplers.data(), flat_separate_samplers.size()) != | 118 | file.Read(flat_bindless_samplers) != flat_bindless_samplers.size()) { |
| 123 | flat_separate_samplers.size() || | ||
| 124 | file.ReadArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) != | ||
| 125 | flat_bindless_samplers.size()) { | ||
| 126 | return false; | 119 | return false; |
| 127 | } | 120 | } |
| 128 | for (const auto& entry : flat_keys) { | 121 | for (const auto& entry : flat_keys) { |
| @@ -145,26 +138,25 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { | |||
| 145 | } | 138 | } |
| 146 | 139 | ||
| 147 | bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { | 140 | bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { |
| 148 | if (file.WriteObject(static_cast<u32>(type)) != 1 || | 141 | if (!file.WriteObject(static_cast<u32>(type)) || |
| 149 | file.WriteObject(static_cast<u32>(code.size())) != 1 || | 142 | !file.WriteObject(static_cast<u32>(code.size())) || |
| 150 | file.WriteObject(static_cast<u32>(code_b.size())) != 1) { | 143 | !file.WriteObject(static_cast<u32>(code_b.size()))) { |
| 151 | return false; | 144 | return false; |
| 152 | } | 145 | } |
| 153 | if (file.WriteArray(code.data(), code.size()) != code.size()) { | 146 | if (file.Write(code) != code.size()) { |
| 154 | return false; | 147 | return false; |
| 155 | } | 148 | } |
| 156 | if (HasProgramA() && file.WriteArray(code_b.data(), code_b.size()) != code_b.size()) { | 149 | if (HasProgramA() && file.Write(code_b) != code_b.size()) { |
| 157 | return false; | 150 | return false; |
| 158 | } | 151 | } |
| 159 | 152 | ||
| 160 | if (file.WriteObject(unique_identifier) != 1 || file.WriteObject(bound_buffer) != 1 || | 153 | if (!file.WriteObject(unique_identifier) || !file.WriteObject(bound_buffer) || |
| 161 | file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) != 1 || | 154 | !file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) || |
| 162 | file.WriteObject(texture_handler_size.value_or(0)) != 1 || | 155 | !file.WriteObject(texture_handler_size.value_or(0)) || !file.WriteObject(graphics_info) || |
| 163 | file.WriteObject(graphics_info) != 1 || file.WriteObject(compute_info) != 1 || | 156 | !file.WriteObject(compute_info) || !file.WriteObject(static_cast<u32>(keys.size())) || |
| 164 | file.WriteObject(static_cast<u32>(keys.size())) != 1 || | 157 | !file.WriteObject(static_cast<u32>(bound_samplers.size())) || |
| 165 | file.WriteObject(static_cast<u32>(bound_samplers.size())) != 1 || | 158 | !file.WriteObject(static_cast<u32>(separate_samplers.size())) || |
| 166 | file.WriteObject(static_cast<u32>(separate_samplers.size())) != 1 || | 159 | !file.WriteObject(static_cast<u32>(bindless_samplers.size()))) { |
| 167 | file.WriteObject(static_cast<u32>(bindless_samplers.size())) != 1) { | ||
| 168 | return false; | 160 | return false; |
| 169 | } | 161 | } |
| 170 | 162 | ||
| @@ -197,13 +189,10 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { | |||
| 197 | BindlessSamplerEntry{address.first, address.second, sampler}); | 189 | BindlessSamplerEntry{address.first, address.second, sampler}); |
| 198 | } | 190 | } |
| 199 | 191 | ||
| 200 | return file.WriteArray(flat_keys.data(), flat_keys.size()) == flat_keys.size() && | 192 | return file.Write(flat_keys) == flat_keys.size() && |
| 201 | file.WriteArray(flat_bound_samplers.data(), flat_bound_samplers.size()) == | 193 | file.Write(flat_bound_samplers) == flat_bound_samplers.size() && |
| 202 | flat_bound_samplers.size() && | 194 | file.Write(flat_separate_samplers) == flat_separate_samplers.size() && |
| 203 | file.WriteArray(flat_separate_samplers.data(), flat_separate_samplers.size()) == | 195 | file.Write(flat_bindless_samplers) == flat_bindless_samplers.size(); |
| 204 | flat_separate_samplers.size() && | ||
| 205 | file.WriteArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) == | ||
| 206 | flat_bindless_samplers.size(); | ||
| 207 | } | 196 | } |
| 208 | 197 | ||
| 209 | ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default; | 198 | ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default; |
| @@ -221,7 +210,8 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran | |||
| 221 | return std::nullopt; | 210 | return std::nullopt; |
| 222 | } | 211 | } |
| 223 | 212 | ||
| 224 | Common::FS::IOFile file(GetTransferablePath(), "rb"); | 213 | Common::FS::IOFile file{GetTransferablePath(), Common::FS::FileAccessMode::Read, |
| 214 | Common::FS::FileType::BinaryFile}; | ||
| 225 | if (!file.IsOpen()) { | 215 | if (!file.IsOpen()) { |
| 226 | LOG_INFO(Render_OpenGL, "No transferable shader cache found"); | 216 | LOG_INFO(Render_OpenGL, "No transferable shader cache found"); |
| 227 | is_usable = true; | 217 | is_usable = true; |
| @@ -229,7 +219,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran | |||
| 229 | } | 219 | } |
| 230 | 220 | ||
| 231 | u32 version{}; | 221 | u32 version{}; |
| 232 | if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { | 222 | if (!file.ReadObject(version)) { |
| 233 | LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); | 223 | LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); |
| 234 | return std::nullopt; | 224 | return std::nullopt; |
| 235 | } | 225 | } |
| @@ -249,7 +239,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran | |||
| 249 | 239 | ||
| 250 | // Version is valid, load the shaders | 240 | // Version is valid, load the shaders |
| 251 | std::vector<ShaderDiskCacheEntry> entries; | 241 | std::vector<ShaderDiskCacheEntry> entries; |
| 252 | while (file.Tell() < file.GetSize()) { | 242 | while (static_cast<u64>(file.Tell()) < file.GetSize()) { |
| 253 | ShaderDiskCacheEntry& entry = entries.emplace_back(); | 243 | ShaderDiskCacheEntry& entry = entries.emplace_back(); |
| 254 | if (!entry.Load(file)) { | 244 | if (!entry.Load(file)) { |
| 255 | LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); | 245 | LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); |
| @@ -266,7 +256,8 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled() | |||
| 266 | return {}; | 256 | return {}; |
| 267 | } | 257 | } |
| 268 | 258 | ||
| 269 | Common::FS::IOFile file(GetPrecompiledPath(), "rb"); | 259 | Common::FS::IOFile file{GetPrecompiledPath(), Common::FS::FileAccessMode::Read, |
| 260 | Common::FS::FileType::BinaryFile}; | ||
| 270 | if (!file.IsOpen()) { | 261 | if (!file.IsOpen()) { |
| 271 | LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); | 262 | LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); |
| 272 | return {}; | 263 | return {}; |
| @@ -286,7 +277,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo | |||
| 286 | Common::FS::IOFile& file) { | 277 | Common::FS::IOFile& file) { |
| 287 | // Read compressed file from disk and decompress to virtual precompiled cache file | 278 | // Read compressed file from disk and decompress to virtual precompiled cache file |
| 288 | std::vector<u8> compressed(file.GetSize()); | 279 | std::vector<u8> compressed(file.GetSize()); |
| 289 | file.ReadBytes(compressed.data(), compressed.size()); | 280 | if (file.Read(compressed) != file.GetSize()) { |
| 281 | return std::nullopt; | ||
| 282 | } | ||
| 290 | const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed); | 283 | const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed); |
| 291 | SaveArrayToPrecompiled(decompressed.data(), decompressed.size()); | 284 | SaveArrayToPrecompiled(decompressed.data(), decompressed.size()); |
| 292 | precompiled_cache_virtual_file_offset = 0; | 285 | precompiled_cache_virtual_file_offset = 0; |
| @@ -321,9 +314,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo | |||
| 321 | } | 314 | } |
| 322 | 315 | ||
| 323 | void ShaderDiskCacheOpenGL::InvalidateTransferable() { | 316 | void ShaderDiskCacheOpenGL::InvalidateTransferable() { |
| 324 | if (!Common::FS::Delete(GetTransferablePath())) { | 317 | if (!Common::FS::RemoveFile(GetTransferablePath())) { |
| 325 | LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", | 318 | LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", |
| 326 | GetTransferablePath()); | 319 | Common::FS::PathToUTF8String(GetTransferablePath())); |
| 327 | } | 320 | } |
| 328 | InvalidatePrecompiled(); | 321 | InvalidatePrecompiled(); |
| 329 | } | 322 | } |
| @@ -332,8 +325,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() { | |||
| 332 | // Clear virtaul precompiled cache file | 325 | // Clear virtaul precompiled cache file |
| 333 | precompiled_cache_virtual_file.Resize(0); | 326 | precompiled_cache_virtual_file.Resize(0); |
| 334 | 327 | ||
| 335 | if (!Common::FS::Delete(GetPrecompiledPath())) { | 328 | if (!Common::FS::RemoveFile(GetPrecompiledPath())) { |
| 336 | LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); | 329 | LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", |
| 330 | Common::FS::PathToUTF8String(GetPrecompiledPath())); | ||
| 337 | } | 331 | } |
| 338 | } | 332 | } |
| 339 | 333 | ||
| @@ -398,16 +392,18 @@ Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { | |||
| 398 | const auto transferable_path{GetTransferablePath()}; | 392 | const auto transferable_path{GetTransferablePath()}; |
| 399 | const bool existed = Common::FS::Exists(transferable_path); | 393 | const bool existed = Common::FS::Exists(transferable_path); |
| 400 | 394 | ||
| 401 | Common::FS::IOFile file(transferable_path, "ab"); | 395 | Common::FS::IOFile file{transferable_path, Common::FS::FileAccessMode::Append, |
| 396 | Common::FS::FileType::BinaryFile}; | ||
| 402 | if (!file.IsOpen()) { | 397 | if (!file.IsOpen()) { |
| 403 | LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); | 398 | LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", |
| 399 | Common::FS::PathToUTF8String(transferable_path)); | ||
| 404 | return {}; | 400 | return {}; |
| 405 | } | 401 | } |
| 406 | if (!existed || file.GetSize() == 0) { | 402 | if (!existed || file.GetSize() == 0) { |
| 407 | // If the file didn't exist, write its version | 403 | // If the file didn't exist, write its version |
| 408 | if (file.WriteObject(NativeVersion) != 1) { | 404 | if (!file.WriteObject(NativeVersion)) { |
| 409 | LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}", | 405 | LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}", |
| 410 | transferable_path); | 406 | Common::FS::PathToUTF8String(transferable_path)); |
| 411 | return {}; | 407 | return {}; |
| 412 | } | 408 | } |
| 413 | } | 409 | } |
| @@ -429,51 +425,54 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() { | |||
| 429 | const std::vector<u8> compressed = | 425 | const std::vector<u8> compressed = |
| 430 | Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); | 426 | Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); |
| 431 | 427 | ||
| 432 | const auto precompiled_path{GetPrecompiledPath()}; | 428 | const auto precompiled_path = GetPrecompiledPath(); |
| 433 | Common::FS::IOFile file(precompiled_path, "wb"); | 429 | Common::FS::IOFile file{precompiled_path, Common::FS::FileAccessMode::Write, |
| 430 | Common::FS::FileType::BinaryFile}; | ||
| 434 | 431 | ||
| 435 | if (!file.IsOpen()) { | 432 | if (!file.IsOpen()) { |
| 436 | LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); | 433 | LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", |
| 434 | Common::FS::PathToUTF8String(precompiled_path)); | ||
| 437 | return; | 435 | return; |
| 438 | } | 436 | } |
| 439 | if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) { | 437 | if (file.Write(compressed) != compressed.size()) { |
| 440 | LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", | 438 | LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", |
| 441 | precompiled_path); | 439 | Common::FS::PathToUTF8String(precompiled_path)); |
| 442 | } | 440 | } |
| 443 | } | 441 | } |
| 444 | 442 | ||
| 445 | bool ShaderDiskCacheOpenGL::EnsureDirectories() const { | 443 | bool ShaderDiskCacheOpenGL::EnsureDirectories() const { |
| 446 | const auto CreateDir = [](const std::string& dir) { | 444 | const auto CreateDir = [](const std::filesystem::path& dir) { |
| 447 | if (!Common::FS::CreateDir(dir)) { | 445 | if (!Common::FS::CreateDir(dir)) { |
| 448 | LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); | 446 | LOG_ERROR(Render_OpenGL, "Failed to create directory={}", |
| 447 | Common::FS::PathToUTF8String(dir)); | ||
| 449 | return false; | 448 | return false; |
| 450 | } | 449 | } |
| 451 | return true; | 450 | return true; |
| 452 | }; | 451 | }; |
| 453 | 452 | ||
| 454 | return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) && | 453 | return CreateDir(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir)) && |
| 455 | CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && | 454 | CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && |
| 456 | CreateDir(GetPrecompiledDir()); | 455 | CreateDir(GetPrecompiledDir()); |
| 457 | } | 456 | } |
| 458 | 457 | ||
| 459 | std::string ShaderDiskCacheOpenGL::GetTransferablePath() const { | 458 | std::filesystem::path ShaderDiskCacheOpenGL::GetTransferablePath() const { |
| 460 | return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); | 459 | return GetTransferableDir() / fmt::format("{}.bin", GetTitleID()); |
| 461 | } | 460 | } |
| 462 | 461 | ||
| 463 | std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { | 462 | std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledPath() const { |
| 464 | return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); | 463 | return GetPrecompiledDir() / fmt::format("{}.bin", GetTitleID()); |
| 465 | } | 464 | } |
| 466 | 465 | ||
| 467 | std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { | 466 | std::filesystem::path ShaderDiskCacheOpenGL::GetTransferableDir() const { |
| 468 | return GetBaseDir() + DIR_SEP "transferable"; | 467 | return GetBaseDir() / "transferable"; |
| 469 | } | 468 | } |
| 470 | 469 | ||
| 471 | std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const { | 470 | std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledDir() const { |
| 472 | return GetBaseDir() + DIR_SEP "precompiled"; | 471 | return GetBaseDir() / "precompiled"; |
| 473 | } | 472 | } |
| 474 | 473 | ||
| 475 | std::string ShaderDiskCacheOpenGL::GetBaseDir() const { | 474 | std::filesystem::path ShaderDiskCacheOpenGL::GetBaseDir() const { |
| 476 | return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl"; | 475 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "opengl"; |
| 477 | } | 476 | } |
| 478 | 477 | ||
| 479 | std::string ShaderDiskCacheOpenGL::GetTitleID() const { | 478 | std::string ShaderDiskCacheOpenGL::GetTitleID() const { |
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h index aef841c1d..f8bc23868 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <filesystem> | ||
| 7 | #include <optional> | 8 | #include <optional> |
| 8 | #include <string> | 9 | #include <string> |
| 9 | #include <tuple> | 10 | #include <tuple> |
| @@ -108,19 +109,19 @@ private: | |||
| 108 | bool EnsureDirectories() const; | 109 | bool EnsureDirectories() const; |
| 109 | 110 | ||
| 110 | /// Gets current game's transferable file path | 111 | /// Gets current game's transferable file path |
| 111 | std::string GetTransferablePath() const; | 112 | std::filesystem::path GetTransferablePath() const; |
| 112 | 113 | ||
| 113 | /// Gets current game's precompiled file path | 114 | /// Gets current game's precompiled file path |
| 114 | std::string GetPrecompiledPath() const; | 115 | std::filesystem::path GetPrecompiledPath() const; |
| 115 | 116 | ||
| 116 | /// Get user's transferable directory path | 117 | /// Get user's transferable directory path |
| 117 | std::string GetTransferableDir() const; | 118 | std::filesystem::path GetTransferableDir() const; |
| 118 | 119 | ||
| 119 | /// Get user's precompiled directory path | 120 | /// Get user's precompiled directory path |
| 120 | std::string GetPrecompiledDir() const; | 121 | std::filesystem::path GetPrecompiledDir() const; |
| 121 | 122 | ||
| 122 | /// Get user's shader directory path | 123 | /// Get user's shader directory path |
| 123 | std::string GetBaseDir() const; | 124 | std::filesystem::path GetBaseDir() const; |
| 124 | 125 | ||
| 125 | /// Get current game's title id | 126 | /// Get current game's title id |
| 126 | std::string GetTitleID() const; | 127 | std::string GetTitleID() const; |
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index cc2e499f9..a718bff7a 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -155,6 +155,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 155 | 155 | ||
| 156 | ++m_current_frame; | 156 | ++m_current_frame; |
| 157 | 157 | ||
| 158 | gpu.RendererFrameEndNotify(); | ||
| 158 | rasterizer.TickFrame(); | 159 | rasterizer.TickFrame(); |
| 159 | 160 | ||
| 160 | context->SwapBuffers(); | 161 | context->SwapBuffers(); |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2e0cf4232..3986eb172 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -154,6 +154,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 154 | if (swapchain.Present(render_semaphore)) { | 154 | if (swapchain.Present(render_semaphore)) { |
| 155 | blit_screen.Recreate(); | 155 | blit_screen.Recreate(); |
| 156 | } | 156 | } |
| 157 | gpu.RendererFrameEndNotify(); | ||
| 157 | rasterizer.TickFrame(); | 158 | rasterizer.TickFrame(); |
| 158 | } | 159 | } |
| 159 | 160 | ||
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp index 7a9d00d4f..f0ee76519 100644 --- a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #ifdef HAS_NSIGHT_AFTERMATH | 5 | #ifdef HAS_NSIGHT_AFTERMATH |
| 6 | 6 | ||
| 7 | #include <mutex> | 7 | #include <mutex> |
| 8 | #include <span> | ||
| 8 | #include <string> | 9 | #include <string> |
| 9 | #include <string_view> | 10 | #include <string_view> |
| 10 | #include <utility> | 11 | #include <utility> |
| @@ -12,9 +13,10 @@ | |||
| 12 | 13 | ||
| 13 | #include <fmt/format.h> | 14 | #include <fmt/format.h> |
| 14 | 15 | ||
| 15 | #include "common/common_paths.h" | ||
| 16 | #include "common/common_types.h" | 16 | #include "common/common_types.h" |
| 17 | #include "common/file_util.h" | 17 | #include "common/fs/file.h" |
| 18 | #include "common/fs/fs.h" | ||
| 19 | #include "common/fs/path_util.h" | ||
| 18 | #include "common/logging/log.h" | 20 | #include "common/logging/log.h" |
| 19 | #include "common/scope_exit.h" | 21 | #include "common/scope_exit.h" |
| 20 | #include "video_core/vulkan_common/nsight_aftermath_tracker.h" | 22 | #include "video_core/vulkan_common/nsight_aftermath_tracker.h" |
| @@ -46,9 +48,9 @@ NsightAftermathTracker::NsightAftermathTracker() { | |||
| 46 | LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers"); | 48 | LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers"); |
| 47 | return; | 49 | return; |
| 48 | } | 50 | } |
| 49 | dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash"; | 51 | dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash"; |
| 50 | 52 | ||
| 51 | void(Common::FS::DeleteDirRecursively(dump_dir)); | 53 | void(Common::FS::RemoveDirRecursively(dump_dir)); |
| 52 | if (!Common::FS::CreateDir(dump_dir)) { | 54 | if (!Common::FS::CreateDir(dump_dir)) { |
| 53 | LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); | 55 | LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); |
| 54 | return; | 56 | return; |
| @@ -60,7 +62,8 @@ NsightAftermathTracker::NsightAftermathTracker() { | |||
| 60 | LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); | 62 | LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); |
| 61 | return; | 63 | return; |
| 62 | } | 64 | } |
| 63 | LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir); | 65 | LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", |
| 66 | Common::FS::PathToUTF8String(dump_dir)); | ||
| 64 | initialized = true; | 67 | initialized = true; |
| 65 | } | 68 | } |
| 66 | 69 | ||
| @@ -89,12 +92,15 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const { | |||
| 89 | return; | 92 | return; |
| 90 | } | 93 | } |
| 91 | 94 | ||
| 92 | Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); | 95 | const auto shader_file = dump_dir / fmt::format("source_{:016x}.spv", hash.hash); |
| 96 | |||
| 97 | Common::FS::IOFile file{shader_file, Common::FS::FileAccessMode::Write, | ||
| 98 | Common::FS::FileType::BinaryFile}; | ||
| 93 | if (!file.IsOpen()) { | 99 | if (!file.IsOpen()) { |
| 94 | LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); | 100 | LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); |
| 95 | return; | 101 | return; |
| 96 | } | 102 | } |
| 97 | if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) { | 103 | if (file.Write(spirv) != spirv.size()) { |
| 98 | LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash); | 104 | LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash); |
| 99 | return; | 105 | return; |
| 100 | } | 106 | } |
| @@ -129,22 +135,24 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump, | |||
| 129 | return; | 135 | return; |
| 130 | } | 136 | } |
| 131 | 137 | ||
| 132 | const std::string base_name = [this] { | 138 | std::filesystem::path base_name = [this] { |
| 133 | const int id = dump_id++; | 139 | const int id = dump_id++; |
| 134 | if (id == 0) { | 140 | if (id == 0) { |
| 135 | return fmt::format("{}/crash.nv-gpudmp", dump_dir); | 141 | return dump_dir / "crash.nv-gpudmp"; |
| 136 | } else { | 142 | } else { |
| 137 | return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id); | 143 | return dump_dir / fmt::format("crash_{}.nv-gpudmp", id); |
| 138 | } | 144 | } |
| 139 | }(); | 145 | }(); |
| 140 | 146 | ||
| 141 | std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); | 147 | std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); |
| 142 | if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { | 148 | if (Common::FS::WriteStringToFile(base_name, Common::FS::FileType::BinaryFile, dump_view) != |
| 149 | gpu_crash_dump_size) { | ||
| 143 | LOG_ERROR(Render_Vulkan, "Failed to write dump file"); | 150 | LOG_ERROR(Render_Vulkan, "Failed to write dump file"); |
| 144 | return; | 151 | return; |
| 145 | } | 152 | } |
| 146 | const std::string_view json_view(json.data(), json.size()); | 153 | const std::string_view json_view(json.data(), json.size()); |
| 147 | if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { | 154 | if (Common::FS::WriteStringToFile(base_name.concat(".json"), Common::FS::FileType::TextFile, |
| 155 | json_view) != json.size()) { | ||
| 148 | LOG_ERROR(Render_Vulkan, "Failed to write JSON"); | 156 | LOG_ERROR(Render_Vulkan, "Failed to write JSON"); |
| 149 | return; | 157 | return; |
| 150 | } | 158 | } |
| @@ -161,16 +169,17 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_ | |||
| 161 | return; | 169 | return; |
| 162 | } | 170 | } |
| 163 | 171 | ||
| 164 | const std::string path = | 172 | const auto path = |
| 165 | fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]); | 173 | dump_dir / fmt::format("shader_{:016x}{:016x}.nvdbg", identifier.id[0], identifier.id[1]); |
| 166 | Common::FS::IOFile file(path, "wb"); | 174 | Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write, |
| 175 | Common::FS::FileType::BinaryFile}; | ||
| 167 | if (!file.IsOpen()) { | 176 | if (!file.IsOpen()) { |
| 168 | LOG_ERROR(Render_Vulkan, "Failed to create file {}", path); | 177 | LOG_ERROR(Render_Vulkan, "Failed to create file {}", Common::FS::PathToUTF8String(path)); |
| 169 | return; | 178 | return; |
| 170 | } | 179 | } |
| 171 | if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) != | 180 | if (file.WriteSpan(std::span(static_cast<const u8*>(shader_debug_info), |
| 172 | shader_debug_info_size) { | 181 | shader_debug_info_size)) != shader_debug_info_size) { |
| 173 | LOG_ERROR(Render_Vulkan, "Failed to write file {}", path); | 182 | LOG_ERROR(Render_Vulkan, "Failed to write file {}", Common::FS::PathToUTF8String(path)); |
| 174 | return; | 183 | return; |
| 175 | } | 184 | } |
| 176 | } | 185 | } |
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.h b/src/video_core/vulkan_common/nsight_aftermath_tracker.h index 1ce8d4e8e..4fe2b14d9 100644 --- a/src/video_core/vulkan_common/nsight_aftermath_tracker.h +++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <filesystem> | ||
| 7 | #include <mutex> | 8 | #include <mutex> |
| 8 | #include <string> | 9 | #include <string> |
| 9 | #include <vector> | 10 | #include <vector> |
| @@ -54,7 +55,7 @@ private: | |||
| 54 | 55 | ||
| 55 | mutable std::mutex mutex; | 56 | mutable std::mutex mutex; |
| 56 | 57 | ||
| 57 | std::string dump_dir; | 58 | std::filesystem::path dump_dir; |
| 58 | int dump_id = 0; | 59 | int dump_id = 0; |
| 59 | 60 | ||
| 60 | bool initialized = false; | 61 | bool initialized = false; |
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp index 557871d81..22833fa56 100644 --- a/src/video_core/vulkan_common/vulkan_library.cpp +++ b/src/video_core/vulkan_common/vulkan_library.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | #include <string> | 6 | #include <string> |
| 7 | 7 | ||
| 8 | #include "common/dynamic_library.h" | 8 | #include "common/dynamic_library.h" |
| 9 | #include "common/file_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "video_core/vulkan_common/vulkan_library.h" | 10 | #include "video_core/vulkan_common/vulkan_library.h" |
| 11 | 11 | ||
| 12 | namespace Vulkan { | 12 | namespace Vulkan { |
| @@ -18,9 +18,9 @@ Common::DynamicLibrary OpenLibrary() { | |||
| 18 | char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); | 18 | char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); |
| 19 | if (!libvulkan_env || !library.Open(libvulkan_env)) { | 19 | if (!libvulkan_env || !library.Open(libvulkan_env)) { |
| 20 | // Use the libvulkan.dylib from the application bundle. | 20 | // Use the libvulkan.dylib from the application bundle. |
| 21 | const std::string filename = | 21 | const auto filename = |
| 22 | Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; | 22 | Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib"; |
| 23 | void(library.Open(filename.c_str())); | 23 | void(library.Open(Common::FS::PathToUTF8String(filename).c_str())); |
| 24 | } | 24 | } |
| 25 | #else | 25 | #else |
| 26 | std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); | 26 | std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1); |
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index 0a4c48b3d..62fd1141c 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp | |||
| @@ -10,7 +10,7 @@ | |||
| 10 | #include <QScrollArea> | 10 | #include <QScrollArea> |
| 11 | #include <QStandardItemModel> | 11 | #include <QStandardItemModel> |
| 12 | #include <QVBoxLayout> | 12 | #include <QVBoxLayout> |
| 13 | #include "common/file_util.h" | 13 | #include "common/fs/path_util.h" |
| 14 | #include "common/string_util.h" | 14 | #include "common/string_util.h" |
| 15 | #include "core/constants.h" | 15 | #include "core/constants.h" |
| 16 | #include "core/hle/lock.h" | 16 | #include "core/hle/lock.h" |
| @@ -26,9 +26,10 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) { | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | QString GetImagePath(Common::UUID uuid) { | 28 | QString GetImagePath(Common::UUID uuid) { |
| 29 | const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 29 | const auto path = |
| 30 | "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; | 30 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / |
| 31 | return QString::fromStdString(path); | 31 | fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); |
| 32 | return QString::fromStdString(Common::FS::PathToUTF8String(path)); | ||
| 32 | } | 33 | } |
| 33 | 34 | ||
| 34 | QPixmap GetIcon(Common::UUID uuid) { | 35 | QPixmap GetIcon(Common::UUID uuid) { |
diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index 653486493..a9a095d58 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp | |||
| @@ -404,12 +404,16 @@ void QtSoftwareKeyboardDialog::ShowTextCheckDialog( | |||
| 404 | 404 | ||
| 405 | OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), | 405 | OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), |
| 406 | tr("Cancel"), tr("OK"), Qt::AlignCenter); | 406 | tr("Cancel"), tr("OK"), Qt::AlignCenter); |
| 407 | if (dialog.exec() == QDialog::Accepted) { | 407 | if (dialog.exec() != QDialog::Accepted) { |
| 408 | emit SubmitNormalText(SwkbdResult::Ok, current_text); | 408 | StartInputThread(); |
| 409 | break; | 409 | break; |
| 410 | } | 410 | } |
| 411 | 411 | ||
| 412 | StartInputThread(); | 412 | auto text = ui->topOSK->currentIndex() == 1 |
| 413 | ? ui->text_edit_osk->toPlainText().toStdU16String() | ||
| 414 | : ui->line_edit_osk->text().toStdU16String(); | ||
| 415 | |||
| 416 | emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); | ||
| 413 | break; | 417 | break; |
| 414 | } | 418 | } |
| 415 | } | 419 | } |
| @@ -480,11 +484,7 @@ void QtSoftwareKeyboardDialog::open() { | |||
| 480 | void QtSoftwareKeyboardDialog::reject() { | 484 | void QtSoftwareKeyboardDialog::reject() { |
| 481 | // Pressing the ESC key in a dialog calls QDialog::reject(). | 485 | // Pressing the ESC key in a dialog calls QDialog::reject(). |
| 482 | // We will override this behavior to the "Cancel" action on the software keyboard. | 486 | // We will override this behavior to the "Cancel" action on the software keyboard. |
| 483 | if (is_inline) { | 487 | TranslateButtonPress(HIDButton::X); |
| 484 | emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); | ||
| 485 | } else { | ||
| 486 | emit SubmitNormalText(SwkbdResult::Cancel, current_text); | ||
| 487 | } | ||
| 488 | } | 488 | } |
| 489 | 489 | ||
| 490 | void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { | 490 | void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { |
| @@ -720,21 +720,9 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() { | |||
| 720 | ui->line_edit_osk->setFocus(); | 720 | ui->line_edit_osk->setFocus(); |
| 721 | }); | 721 | }); |
| 722 | 722 | ||
| 723 | connect(ui->line_edit_osk, &QLineEdit::returnPressed, [this] { | 723 | connect( |
| 724 | switch (bottom_osk_index) { | 724 | ui->line_edit_osk, &QLineEdit::returnPressed, this, |
| 725 | case BottomOSKIndex::LowerCase: | 725 | [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); |
| 726 | ui->button_ok->click(); | ||
| 727 | break; | ||
| 728 | case BottomOSKIndex::UpperCase: | ||
| 729 | ui->button_ok_shift->click(); | ||
| 730 | break; | ||
| 731 | case BottomOSKIndex::NumberPad: | ||
| 732 | ui->button_ok_num->click(); | ||
| 733 | break; | ||
| 734 | default: | ||
| 735 | break; | ||
| 736 | } | ||
| 737 | }); | ||
| 738 | 726 | ||
| 739 | ui->line_edit_osk->setPlaceholderText( | 727 | ui->line_edit_osk->setPlaceholderText( |
| 740 | QString::fromStdU16String(initialize_parameters.guide_text)); | 728 | QString::fromStdU16String(initialize_parameters.guide_text)); |
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 93e3a4f6f..34d3feb55 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | #include <QWebEngineUrlScheme> | 12 | #include <QWebEngineUrlScheme> |
| 13 | #endif | 13 | #endif |
| 14 | 14 | ||
| 15 | #include "common/file_util.h" | 15 | #include "common/fs/path_util.h" |
| 16 | #include "core/core.h" | 16 | #include "core/core.h" |
| 17 | #include "core/frontend/input_interpreter.h" | 17 | #include "core/frontend/input_interpreter.h" |
| 18 | #include "input_common/keyboard.h" | 18 | #include "input_common/keyboard.h" |
| @@ -322,21 +322,25 @@ void QtNXWebEngineView::LoadExtractedFonts() { | |||
| 322 | QWebEngineScript nx_font_css; | 322 | QWebEngineScript nx_font_css; |
| 323 | QWebEngineScript load_nx_font; | 323 | QWebEngineScript load_nx_font; |
| 324 | 324 | ||
| 325 | const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( | 325 | auto fonts_dir_str = Common::FS::PathToUTF8String( |
| 326 | fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); | 326 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); |
| 327 | |||
| 328 | std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); | ||
| 329 | |||
| 330 | const auto fonts_dir = QString::fromStdString(fonts_dir_str); | ||
| 327 | 331 | ||
| 328 | nx_font_css.setName(QStringLiteral("nx_font_css.js")); | 332 | nx_font_css.setName(QStringLiteral("nx_font_css.js")); |
| 329 | load_nx_font.setName(QStringLiteral("load_nx_font.js")); | 333 | load_nx_font.setName(QStringLiteral("load_nx_font.js")); |
| 330 | 334 | ||
| 331 | nx_font_css.setSourceCode( | 335 | nx_font_css.setSourceCode( |
| 332 | QString::fromStdString(NX_FONT_CSS) | 336 | QString::fromStdString(NX_FONT_CSS) |
| 333 | .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) | 337 | .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) |
| 334 | .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) | 338 | .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) |
| 335 | .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) | 339 | .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) |
| 336 | .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) | 340 | .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) |
| 337 | .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) | 341 | .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) |
| 338 | .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) | 342 | .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) |
| 339 | .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); | 343 | .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); |
| 340 | load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); | 344 | load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); |
| 341 | 345 | ||
| 342 | nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); | 346 | nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e80a3df77..eb58bfa5b 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -5,8 +5,8 @@ | |||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <QKeySequence> | 6 | #include <QKeySequence> |
| 7 | #include <QSettings> | 7 | #include <QSettings> |
| 8 | #include "common/common_paths.h" | 8 | #include "common/fs/fs.h" |
| 9 | #include "common/file_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "core/core.h" | 10 | #include "core/core.h" |
| 11 | #include "core/hle/service/acc/profile_manager.h" | 11 | #include "core/hle/service/acc/profile_manager.h" |
| 12 | #include "core/hle/service/hid/controllers/npad.h" | 12 | #include "core/hle/service/hid/controllers/npad.h" |
| @@ -243,27 +243,27 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ | |||
| 243 | // clang-format on | 243 | // clang-format on |
| 244 | 244 | ||
| 245 | void Config::Initialize(const std::string& config_name) { | 245 | void Config::Initialize(const std::string& config_name) { |
| 246 | const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); | ||
| 247 | const auto config_file = fmt::format("{}.ini", config_name); | ||
| 248 | |||
| 246 | switch (type) { | 249 | switch (type) { |
| 247 | case ConfigType::GlobalConfig: | 250 | case ConfigType::GlobalConfig: |
| 248 | qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), | 251 | qt_config_loc = FS::PathToUTF8String(fs_config_loc / config_file); |
| 249 | config_name); | 252 | void(FS::CreateParentDir(qt_config_loc)); |
| 250 | FS::CreateFullPath(qt_config_loc); | ||
| 251 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), | 253 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), |
| 252 | QSettings::IniFormat); | 254 | QSettings::IniFormat); |
| 253 | Reload(); | 255 | Reload(); |
| 254 | break; | 256 | break; |
| 255 | case ConfigType::PerGameConfig: | 257 | case ConfigType::PerGameConfig: |
| 256 | qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", | 258 | qt_config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / config_file); |
| 257 | FS::GetUserPath(FS::UserPath::ConfigDir), config_name); | 259 | void(FS::CreateParentDir(qt_config_loc)); |
| 258 | FS::CreateFullPath(qt_config_loc); | ||
| 259 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), | 260 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), |
| 260 | QSettings::IniFormat); | 261 | QSettings::IniFormat); |
| 261 | Reload(); | 262 | Reload(); |
| 262 | break; | 263 | break; |
| 263 | case ConfigType::InputProfile: | 264 | case ConfigType::InputProfile: |
| 264 | qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", | 265 | qt_config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); |
| 265 | FS::GetUserPath(FS::UserPath::ConfigDir), config_name); | 266 | void(FS::CreateParentDir(qt_config_loc)); |
| 266 | FS::CreateFullPath(qt_config_loc); | ||
| 267 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), | 267 | qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), |
| 268 | QSettings::IniFormat); | 268 | QSettings::IniFormat); |
| 269 | break; | 269 | break; |
| @@ -514,6 +514,13 @@ void Config::ReadControlValues() { | |||
| 514 | ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat(); | 514 | ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat(); |
| 515 | 515 | ||
| 516 | ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true); | 516 | ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true); |
| 517 | |||
| 518 | // Disable docked mode if handheld is selected | ||
| 519 | const auto controller_type = Settings::values.players.GetValue()[0].controller_type; | ||
| 520 | if (controller_type == Settings::ControllerType::Handheld) { | ||
| 521 | Settings::values.use_docked_mode.SetValue(false); | ||
| 522 | } | ||
| 523 | |||
| 517 | ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), | 524 | ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), |
| 518 | true); | 525 | true); |
| 519 | ReadSettingGlobal(Settings::values.enable_accurate_vibrations, | 526 | ReadSettingGlobal(Settings::values.enable_accurate_vibrations, |
| @@ -591,30 +598,34 @@ void Config::ReadDataStorageValues() { | |||
| 591 | qt_config->beginGroup(QStringLiteral("Data Storage")); | 598 | qt_config->beginGroup(QStringLiteral("Data Storage")); |
| 592 | 599 | ||
| 593 | Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); | 600 | Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); |
| 594 | FS::GetUserPath(FS::UserPath::NANDDir, | 601 | FS::SetYuzuPath( |
| 595 | qt_config | 602 | FS::YuzuPath::NANDDir, |
| 596 | ->value(QStringLiteral("nand_directory"), | 603 | qt_config |
| 597 | QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))) | 604 | ->value(QStringLiteral("nand_directory"), |
| 598 | .toString() | 605 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))) |
| 599 | .toStdString()); | 606 | .toString() |
| 600 | FS::GetUserPath(FS::UserPath::SDMCDir, | 607 | .toStdString()); |
| 601 | qt_config | 608 | FS::SetYuzuPath( |
| 602 | ->value(QStringLiteral("sdmc_directory"), | 609 | FS::YuzuPath::SDMCDir, |
| 603 | QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))) | 610 | qt_config |
| 604 | .toString() | 611 | ->value(QStringLiteral("sdmc_directory"), |
| 605 | .toStdString()); | 612 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))) |
| 606 | FS::GetUserPath(FS::UserPath::LoadDir, | 613 | .toString() |
| 607 | qt_config | 614 | .toStdString()); |
| 608 | ->value(QStringLiteral("load_directory"), | 615 | FS::SetYuzuPath( |
| 609 | QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))) | 616 | FS::YuzuPath::LoadDir, |
| 610 | .toString() | 617 | qt_config |
| 611 | .toStdString()); | 618 | ->value(QStringLiteral("load_directory"), |
| 612 | FS::GetUserPath(FS::UserPath::DumpDir, | 619 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))) |
| 613 | qt_config | 620 | .toString() |
| 614 | ->value(QStringLiteral("dump_directory"), | 621 | .toStdString()); |
| 615 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) | 622 | FS::SetYuzuPath( |
| 616 | .toString() | 623 | FS::YuzuPath::DumpDir, |
| 617 | .toStdString()); | 624 | qt_config |
| 625 | ->value(QStringLiteral("dump_directory"), | ||
| 626 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))) | ||
| 627 | .toString() | ||
| 628 | .toStdString()); | ||
| 618 | Settings::values.gamecard_inserted = | 629 | Settings::values.gamecard_inserted = |
| 619 | ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); | 630 | ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); |
| 620 | Settings::values.gamecard_current_game = | 631 | Settings::values.gamecard_current_game = |
| @@ -736,10 +747,16 @@ void Config::ReadPathValues() { | |||
| 736 | void Config::ReadCpuValues() { | 747 | void Config::ReadCpuValues() { |
| 737 | qt_config->beginGroup(QStringLiteral("Cpu")); | 748 | qt_config->beginGroup(QStringLiteral("Cpu")); |
| 738 | 749 | ||
| 739 | if (global) { | 750 | ReadSettingGlobal(Settings::values.cpu_accuracy, QStringLiteral("cpu_accuracy"), 0); |
| 740 | Settings::values.cpu_accuracy = static_cast<Settings::CPUAccuracy>( | 751 | |
| 741 | ReadSetting(QStringLiteral("cpu_accuracy"), 0).toInt()); | 752 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_unfuse_fma, |
| 753 | QStringLiteral("cpuopt_unsafe_unfuse_fma"), true); | ||
| 754 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error, | ||
| 755 | QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); | ||
| 756 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, | ||
| 757 | QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); | ||
| 742 | 758 | ||
| 759 | if (global) { | ||
| 743 | Settings::values.cpuopt_page_tables = | 760 | Settings::values.cpuopt_page_tables = |
| 744 | ReadSetting(QStringLiteral("cpuopt_page_tables"), true).toBool(); | 761 | ReadSetting(QStringLiteral("cpuopt_page_tables"), true).toBool(); |
| 745 | Settings::values.cpuopt_block_linking = | 762 | Settings::values.cpuopt_block_linking = |
| @@ -756,13 +773,6 @@ void Config::ReadCpuValues() { | |||
| 756 | ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); | 773 | ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); |
| 757 | Settings::values.cpuopt_reduce_misalign_checks = | 774 | Settings::values.cpuopt_reduce_misalign_checks = |
| 758 | ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); | 775 | ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); |
| 759 | |||
| 760 | Settings::values.cpuopt_unsafe_unfuse_fma = | ||
| 761 | ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); | ||
| 762 | Settings::values.cpuopt_unsafe_reduce_fp_error = | ||
| 763 | ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); | ||
| 764 | Settings::values.cpuopt_unsafe_inaccurate_nan = | ||
| 765 | ReadSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true).toBool(); | ||
| 766 | } | 776 | } |
| 767 | 777 | ||
| 768 | qt_config->endGroup(); | 778 | qt_config->endGroup(); |
| @@ -811,11 +821,11 @@ void Config::ReadScreenshotValues() { | |||
| 811 | 821 | ||
| 812 | UISettings::values.enable_screenshot_save_as = | 822 | UISettings::values.enable_screenshot_save_as = |
| 813 | ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); | 823 | ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); |
| 814 | FS::GetUserPath( | 824 | FS::SetYuzuPath( |
| 815 | FS::UserPath::ScreenshotsDir, | 825 | FS::YuzuPath::ScreenshotsDir, |
| 816 | qt_config | 826 | qt_config |
| 817 | ->value(QStringLiteral("screenshot_path"), | 827 | ->value(QStringLiteral("screenshot_path"), |
| 818 | QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))) | 828 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir))) |
| 819 | .toString() | 829 | .toString() |
| 820 | .toStdString()); | 830 | .toStdString()); |
| 821 | 831 | ||
| @@ -869,17 +879,14 @@ void Config::ReadSystemValues() { | |||
| 869 | } | 879 | } |
| 870 | } | 880 | } |
| 871 | 881 | ||
| 872 | bool custom_rtc_enabled; | 882 | if (global) { |
| 873 | ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false); | 883 | const auto custom_rtc_enabled = |
| 874 | bool custom_rtc_global = | 884 | ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); |
| 875 | global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool(); | ||
| 876 | Settings::values.custom_rtc.SetGlobal(custom_rtc_global); | ||
| 877 | if (global || !custom_rtc_global) { | ||
| 878 | if (custom_rtc_enabled) { | 885 | if (custom_rtc_enabled) { |
| 879 | Settings::values.custom_rtc.SetValue( | 886 | Settings::values.custom_rtc = |
| 880 | std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong())); | 887 | std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); |
| 881 | } else { | 888 | } else { |
| 882 | Settings::values.custom_rtc.SetValue(std::nullopt); | 889 | Settings::values.custom_rtc = std::nullopt; |
| 883 | } | 890 | } |
| 884 | } | 891 | } |
| 885 | 892 | ||
| @@ -1217,17 +1224,17 @@ void Config::SaveDataStorageValues() { | |||
| 1217 | 1224 | ||
| 1218 | WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); | 1225 | WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); |
| 1219 | WriteSetting(QStringLiteral("nand_directory"), | 1226 | WriteSetting(QStringLiteral("nand_directory"), |
| 1220 | QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)), | 1227 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)), |
| 1221 | QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))); | 1228 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); |
| 1222 | WriteSetting(QStringLiteral("sdmc_directory"), | 1229 | WriteSetting(QStringLiteral("sdmc_directory"), |
| 1223 | QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)), | 1230 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)), |
| 1224 | QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))); | 1231 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); |
| 1225 | WriteSetting(QStringLiteral("load_directory"), | 1232 | WriteSetting(QStringLiteral("load_directory"), |
| 1226 | QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)), | 1233 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)), |
| 1227 | QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))); | 1234 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); |
| 1228 | WriteSetting(QStringLiteral("dump_directory"), | 1235 | WriteSetting(QStringLiteral("dump_directory"), |
| 1229 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), | 1236 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)), |
| 1230 | QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); | 1237 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); |
| 1231 | WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); | 1238 | WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); |
| 1232 | WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, | 1239 | WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, |
| 1233 | false); | 1240 | false); |
| @@ -1313,10 +1320,19 @@ void Config::SavePathValues() { | |||
| 1313 | void Config::SaveCpuValues() { | 1320 | void Config::SaveCpuValues() { |
| 1314 | qt_config->beginGroup(QStringLiteral("Cpu")); | 1321 | qt_config->beginGroup(QStringLiteral("Cpu")); |
| 1315 | 1322 | ||
| 1316 | if (global) { | 1323 | WriteSettingGlobal(QStringLiteral("cpu_accuracy"), |
| 1317 | WriteSetting(QStringLiteral("cpu_accuracy"), | 1324 | static_cast<u32>(Settings::values.cpu_accuracy.GetValue(global)), |
| 1318 | static_cast<int>(Settings::values.cpu_accuracy), 0); | 1325 | Settings::values.cpu_accuracy.UsingGlobal(), |
| 1326 | static_cast<u32>(Settings::CPUAccuracy::Accurate)); | ||
| 1327 | |||
| 1328 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_unfuse_fma"), | ||
| 1329 | Settings::values.cpuopt_unsafe_unfuse_fma, true); | ||
| 1330 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), | ||
| 1331 | Settings::values.cpuopt_unsafe_reduce_fp_error, true); | ||
| 1332 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), | ||
| 1333 | Settings::values.cpuopt_unsafe_inaccurate_nan, true); | ||
| 1319 | 1334 | ||
| 1335 | if (global) { | ||
| 1320 | WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, | 1336 | WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, |
| 1321 | true); | 1337 | true); |
| 1322 | WriteSetting(QStringLiteral("cpuopt_block_linking"), Settings::values.cpuopt_block_linking, | 1338 | WriteSetting(QStringLiteral("cpuopt_block_linking"), Settings::values.cpuopt_block_linking, |
| @@ -1331,13 +1347,6 @@ void Config::SaveCpuValues() { | |||
| 1331 | WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); | 1347 | WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); |
| 1332 | WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), | 1348 | WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), |
| 1333 | Settings::values.cpuopt_reduce_misalign_checks, true); | 1349 | Settings::values.cpuopt_reduce_misalign_checks, true); |
| 1334 | |||
| 1335 | WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), | ||
| 1336 | Settings::values.cpuopt_unsafe_unfuse_fma, true); | ||
| 1337 | WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), | ||
| 1338 | Settings::values.cpuopt_unsafe_reduce_fp_error, true); | ||
| 1339 | WriteSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), | ||
| 1340 | Settings::values.cpuopt_unsafe_inaccurate_nan, true); | ||
| 1341 | } | 1350 | } |
| 1342 | 1351 | ||
| 1343 | qt_config->endGroup(); | 1352 | qt_config->endGroup(); |
| @@ -1392,7 +1401,7 @@ void Config::SaveScreenshotValues() { | |||
| 1392 | WriteSetting(QStringLiteral("enable_screenshot_save_as"), | 1401 | WriteSetting(QStringLiteral("enable_screenshot_save_as"), |
| 1393 | UISettings::values.enable_screenshot_save_as); | 1402 | UISettings::values.enable_screenshot_save_as); |
| 1394 | WriteSetting(QStringLiteral("screenshot_path"), | 1403 | WriteSetting(QStringLiteral("screenshot_path"), |
| 1395 | QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))); | 1404 | QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir))); |
| 1396 | 1405 | ||
| 1397 | qt_config->endGroup(); | 1406 | qt_config->endGroup(); |
| 1398 | } | 1407 | } |
| @@ -1432,14 +1441,14 @@ void Config::SaveSystemValues() { | |||
| 1432 | Settings::values.rng_seed.GetValue(global).value_or(0), | 1441 | Settings::values.rng_seed.GetValue(global).value_or(0), |
| 1433 | Settings::values.rng_seed.UsingGlobal(), 0); | 1442 | Settings::values.rng_seed.UsingGlobal(), 0); |
| 1434 | 1443 | ||
| 1435 | WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"), | 1444 | if (global) { |
| 1436 | Settings::values.custom_rtc.GetValue(global).has_value(), | 1445 | WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), |
| 1437 | Settings::values.custom_rtc.UsingGlobal(), false); | 1446 | false); |
| 1438 | WriteSettingGlobal( | 1447 | WriteSetting(QStringLiteral("custom_rtc"), |
| 1439 | QStringLiteral("custom_rtc"), | 1448 | QVariant::fromValue<long long>( |
| 1440 | QVariant::fromValue<long long>( | 1449 | Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), |
| 1441 | Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()), | 1450 | 0); |
| 1442 | Settings::values.custom_rtc.UsingGlobal(), 0); | 1451 | } |
| 1443 | 1452 | ||
| 1444 | WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1); | 1453 | WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1); |
| 1445 | 1454 | ||
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 5a2c026b3..ce3355588 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h | |||
| @@ -132,5 +132,6 @@ private: | |||
| 132 | }; | 132 | }; |
| 133 | 133 | ||
| 134 | // These metatype declarations cannot be in common/settings.h because core is devoid of QT | 134 | // These metatype declarations cannot be in common/settings.h because core is devoid of QT |
| 135 | Q_DECLARE_METATYPE(Settings::CPUAccuracy); | ||
| 135 | Q_DECLARE_METATYPE(Settings::RendererBackend); | 136 | Q_DECLARE_METATYPE(Settings::RendererBackend); |
| 136 | Q_DECLARE_METATYPE(Settings::GPUAccuracy); | 137 | Q_DECLARE_METATYPE(Settings::GPUAccuracy); |
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp index 89be4a62d..096e42e94 100644 --- a/src/yuzu/configuration/configuration_shared.cpp +++ b/src/yuzu/configuration/configuration_shared.cpp | |||
| @@ -13,32 +13,29 @@ | |||
| 13 | void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, | 13 | void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, |
| 14 | const QCheckBox* checkbox, | 14 | const QCheckBox* checkbox, |
| 15 | const CheckState& tracker) { | 15 | const CheckState& tracker) { |
| 16 | if (tracker == CheckState::Global) { | 16 | if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { |
| 17 | setting->SetGlobal(true); | ||
| 18 | } else { | ||
| 19 | setting->SetGlobal(false); | ||
| 20 | setting->SetValue(checkbox->checkState()); | 17 | setting->SetValue(checkbox->checkState()); |
| 18 | } else if (!Settings::IsConfiguringGlobal()) { | ||
| 19 | if (tracker == CheckState::Global) { | ||
| 20 | setting->SetGlobal(true); | ||
| 21 | } else { | ||
| 22 | setting->SetGlobal(false); | ||
| 23 | setting->SetValue(checkbox->checkState()); | ||
| 24 | } | ||
| 21 | } | 25 | } |
| 22 | } | 26 | } |
| 23 | 27 | ||
| 24 | void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, | 28 | void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, |
| 25 | const QComboBox* combobox) { | 29 | const QComboBox* combobox) { |
| 26 | if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { | 30 | if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { |
| 27 | setting->SetGlobal(true); | 31 | setting->SetValue(combobox->currentIndex()); |
| 28 | } else { | 32 | } else if (!Settings::IsConfiguringGlobal()) { |
| 29 | setting->SetGlobal(false); | 33 | if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { |
| 30 | setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); | 34 | setting->SetGlobal(true); |
| 31 | } | 35 | } else { |
| 32 | } | 36 | setting->SetGlobal(false); |
| 33 | 37 | setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); | |
| 34 | void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, | 38 | } |
| 35 | const QComboBox* combobox) { | ||
| 36 | if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { | ||
| 37 | setting->SetGlobal(true); | ||
| 38 | } else { | ||
| 39 | setting->SetGlobal(false); | ||
| 40 | setting->SetValue(static_cast<Settings::RendererBackend>( | ||
| 41 | combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); | ||
| 42 | } | 39 | } |
| 43 | } | 40 | } |
| 44 | 41 | ||
| @@ -51,27 +48,6 @@ void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, | |||
| 51 | } | 48 | } |
| 52 | } | 49 | } |
| 53 | 50 | ||
| 54 | void ConfigurationShared::SetPerGameSetting(QComboBox* combobox, | ||
| 55 | const Settings::Setting<int>* setting) { | ||
| 56 | combobox->setCurrentIndex(setting->UsingGlobal() | ||
| 57 | ? ConfigurationShared::USE_GLOBAL_INDEX | ||
| 58 | : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET); | ||
| 59 | } | ||
| 60 | |||
| 61 | void ConfigurationShared::SetPerGameSetting( | ||
| 62 | QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) { | ||
| 63 | combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX | ||
| 64 | : static_cast<int>(setting->GetValue()) + | ||
| 65 | ConfigurationShared::USE_GLOBAL_OFFSET); | ||
| 66 | } | ||
| 67 | |||
| 68 | void ConfigurationShared::SetPerGameSetting( | ||
| 69 | QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) { | ||
| 70 | combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX | ||
| 71 | : static_cast<int>(setting->GetValue()) + | ||
| 72 | ConfigurationShared::USE_GLOBAL_OFFSET); | ||
| 73 | } | ||
| 74 | |||
| 75 | void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) { | 51 | void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) { |
| 76 | if (highlighted) { | 52 | if (highlighted) { |
| 77 | widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }") | 53 | widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }") |
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index 5b344cdbd..1e0ef01ca 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h | |||
| @@ -15,37 +15,45 @@ constexpr int USE_GLOBAL_INDEX = 0; | |||
| 15 | constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; | 15 | constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; |
| 16 | constexpr int USE_GLOBAL_OFFSET = 2; | 16 | constexpr int USE_GLOBAL_OFFSET = 2; |
| 17 | 17 | ||
| 18 | // CheckBoxes require a tracker for their state since we emulate a tristate CheckBox | ||
| 18 | enum class CheckState { | 19 | enum class CheckState { |
| 19 | Off, | 20 | Off, // Checkbox overrides to off/false |
| 20 | On, | 21 | On, // Checkbox overrides to on/true |
| 21 | Global, | 22 | Global, // Checkbox defers to the global state |
| 22 | Count, | 23 | Count, // Simply the number of states, not a valid checkbox state |
| 23 | }; | 24 | }; |
| 24 | 25 | ||
| 25 | // Global-aware apply and set functions | 26 | // Global-aware apply and set functions |
| 26 | 27 | ||
| 28 | // ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting | ||
| 27 | void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, | 29 | void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, |
| 28 | const CheckState& tracker); | 30 | const CheckState& tracker); |
| 29 | void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); | 31 | void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); |
| 30 | void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, | ||
| 31 | const QComboBox* combobox); | ||
| 32 | void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting, | ||
| 33 | const QComboBox* combobox); | ||
| 34 | 32 | ||
| 33 | // Sets a Qt UI element given a Settings::Setting | ||
| 35 | void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); | 34 | void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); |
| 36 | void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting); | ||
| 37 | void SetPerGameSetting(QComboBox* combobox, | ||
| 38 | const Settings::Setting<Settings::RendererBackend>* setting); | ||
| 39 | void SetPerGameSetting(QComboBox* combobox, | ||
| 40 | const Settings::Setting<Settings::GPUAccuracy>* setting); | ||
| 41 | 35 | ||
| 36 | template <typename Type> | ||
| 37 | void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<Type>* setting) { | ||
| 38 | combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX | ||
| 39 | : static_cast<int>(setting->GetValue()) + | ||
| 40 | ConfigurationShared::USE_GLOBAL_OFFSET); | ||
| 41 | } | ||
| 42 | |||
| 43 | // (Un)highlights a Qt UI element | ||
| 42 | void SetHighlight(QWidget* widget, bool highlighted); | 44 | void SetHighlight(QWidget* widget, bool highlighted); |
| 45 | |||
| 46 | // Sets up a QCheckBox like a tristate one, given a Setting | ||
| 43 | void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting, | 47 | void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting, |
| 44 | CheckState& tracker); | 48 | CheckState& tracker); |
| 45 | void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state, | 49 | void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state, |
| 46 | CheckState& tracker); | 50 | CheckState& tracker); |
| 51 | |||
| 52 | // Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls | ||
| 53 | // InsertGlobalItem | ||
| 47 | void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); | 54 | void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); |
| 48 | 55 | ||
| 56 | // Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox | ||
| 49 | void InsertGlobalItem(QComboBox* combobox, int global_index); | 57 | void InsertGlobalItem(QComboBox* combobox, int global_index); |
| 50 | 58 | ||
| 51 | } // namespace ConfigurationShared | 59 | } // namespace ConfigurationShared |
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index f9507e228..fc0191432 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp | |||
| @@ -99,6 +99,9 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | void ConfigureAudio::ApplyConfiguration() { | 101 | void ConfigureAudio::ApplyConfiguration() { |
| 102 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, | ||
| 103 | ui->toggle_audio_stretching, enable_audio_stretching); | ||
| 104 | |||
| 102 | if (Settings::IsConfiguringGlobal()) { | 105 | if (Settings::IsConfiguringGlobal()) { |
| 103 | Settings::values.sink_id = | 106 | Settings::values.sink_id = |
| 104 | ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) | 107 | ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) |
| @@ -108,19 +111,12 @@ void ConfigureAudio::ApplyConfiguration() { | |||
| 108 | .toStdString(); | 111 | .toStdString(); |
| 109 | 112 | ||
| 110 | // Guard if during game and set to game-specific value | 113 | // Guard if during game and set to game-specific value |
| 111 | if (Settings::values.enable_audio_stretching.UsingGlobal()) { | ||
| 112 | Settings::values.enable_audio_stretching.SetValue( | ||
| 113 | ui->toggle_audio_stretching->isChecked()); | ||
| 114 | } | ||
| 115 | if (Settings::values.volume.UsingGlobal()) { | 114 | if (Settings::values.volume.UsingGlobal()) { |
| 116 | Settings::values.volume.SetValue( | 115 | Settings::values.volume.SetValue( |
| 117 | static_cast<float>(ui->volume_slider->sliderPosition()) / | 116 | static_cast<float>(ui->volume_slider->sliderPosition()) / |
| 118 | ui->volume_slider->maximum()); | 117 | ui->volume_slider->maximum()); |
| 119 | } | 118 | } |
| 120 | } else { | 119 | } else { |
| 121 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, | ||
| 122 | ui->toggle_audio_stretching, | ||
| 123 | enable_audio_stretching); | ||
| 124 | if (ui->volume_combo_box->currentIndex() == 0) { | 120 | if (ui->volume_combo_box->currentIndex() == 0) { |
| 125 | Settings::values.volume.SetGlobal(true); | 121 | Settings::values.volume.SetGlobal(true); |
| 126 | } else { | 122 | } else { |
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 4f99bc80f..525c42ff0 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp | |||
| @@ -10,11 +10,14 @@ | |||
| 10 | #include "common/settings.h" | 10 | #include "common/settings.h" |
| 11 | #include "core/core.h" | 11 | #include "core/core.h" |
| 12 | #include "ui_configure_cpu.h" | 12 | #include "ui_configure_cpu.h" |
| 13 | #include "yuzu/configuration/configuration_shared.h" | ||
| 13 | #include "yuzu/configuration/configure_cpu.h" | 14 | #include "yuzu/configuration/configure_cpu.h" |
| 14 | 15 | ||
| 15 | ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) { | 16 | ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) { |
| 16 | ui->setupUi(this); | 17 | ui->setupUi(this); |
| 17 | 18 | ||
| 19 | SetupPerGameUI(); | ||
| 20 | |||
| 18 | SetConfiguration(); | 21 | SetConfiguration(); |
| 19 | 22 | ||
| 20 | connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, | 23 | connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, |
| @@ -29,19 +32,29 @@ void ConfigureCpu::SetConfiguration() { | |||
| 29 | const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); | 32 | const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); |
| 30 | 33 | ||
| 31 | ui->accuracy->setEnabled(runtime_lock); | 34 | ui->accuracy->setEnabled(runtime_lock); |
| 32 | ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy)); | ||
| 33 | UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy)); | ||
| 34 | |||
| 35 | ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); | 35 | ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); |
| 36 | ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma); | ||
| 37 | ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); | 36 | ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); |
| 38 | ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error); | ||
| 39 | ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); | 37 | ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); |
| 40 | ui->cpuopt_unsafe_inaccurate_nan->setChecked(Settings::values.cpuopt_unsafe_inaccurate_nan); | 38 | |
| 39 | ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); | ||
| 40 | ui->cpuopt_unsafe_reduce_fp_error->setChecked( | ||
| 41 | Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); | ||
| 42 | ui->cpuopt_unsafe_inaccurate_nan->setChecked( | ||
| 43 | Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); | ||
| 44 | |||
| 45 | if (Settings::IsConfiguringGlobal()) { | ||
| 46 | ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue())); | ||
| 47 | } else { | ||
| 48 | ConfigurationShared::SetPerGameSetting(ui->accuracy, &Settings::values.cpu_accuracy); | ||
| 49 | ConfigurationShared::SetHighlight(ui->widget_accuracy, | ||
| 50 | !Settings::values.cpu_accuracy.UsingGlobal()); | ||
| 51 | } | ||
| 52 | UpdateGroup(ui->accuracy->currentIndex()); | ||
| 41 | } | 53 | } |
| 42 | 54 | ||
| 43 | void ConfigureCpu::AccuracyUpdated(int index) { | 55 | void ConfigureCpu::AccuracyUpdated(int index) { |
| 44 | if (static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) { | 56 | if (Settings::IsConfiguringGlobal() && |
| 57 | static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) { | ||
| 45 | const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"), | 58 | const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"), |
| 46 | tr("CPU Debug Mode is only intended for developer " | 59 | tr("CPU Debug Mode is only intended for developer " |
| 47 | "use. Are you sure you want to enable this?"), | 60 | "use. Are you sure you want to enable this?"), |
| @@ -54,16 +67,39 @@ void ConfigureCpu::AccuracyUpdated(int index) { | |||
| 54 | } | 67 | } |
| 55 | 68 | ||
| 56 | void ConfigureCpu::UpdateGroup(int index) { | 69 | void ConfigureCpu::UpdateGroup(int index) { |
| 57 | ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) == | 70 | if (!Settings::IsConfiguringGlobal()) { |
| 58 | Settings::CPUAccuracy::Unsafe); | 71 | index -= ConfigurationShared::USE_GLOBAL_OFFSET; |
| 72 | } | ||
| 73 | const auto accuracy = static_cast<Settings::CPUAccuracy>(index); | ||
| 74 | ui->unsafe_group->setVisible(accuracy == Settings::CPUAccuracy::Unsafe); | ||
| 59 | } | 75 | } |
| 60 | 76 | ||
| 61 | void ConfigureCpu::ApplyConfiguration() { | 77 | void ConfigureCpu::ApplyConfiguration() { |
| 62 | Settings::values.cpu_accuracy = | 78 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_unfuse_fma, |
| 63 | static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); | 79 | ui->cpuopt_unsafe_unfuse_fma, |
| 64 | Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked(); | 80 | cpuopt_unsafe_unfuse_fma); |
| 65 | Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked(); | 81 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error, |
| 66 | Settings::values.cpuopt_unsafe_inaccurate_nan = ui->cpuopt_unsafe_inaccurate_nan->isChecked(); | 82 | ui->cpuopt_unsafe_reduce_fp_error, |
| 83 | cpuopt_unsafe_reduce_fp_error); | ||
| 84 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, | ||
| 85 | ui->cpuopt_unsafe_inaccurate_nan, | ||
| 86 | cpuopt_unsafe_inaccurate_nan); | ||
| 87 | |||
| 88 | if (Settings::IsConfiguringGlobal()) { | ||
| 89 | // Guard if during game and set to game-specific value | ||
| 90 | if (Settings::values.cpu_accuracy.UsingGlobal()) { | ||
| 91 | Settings::values.cpu_accuracy.SetValue( | ||
| 92 | static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex())); | ||
| 93 | } | ||
| 94 | } else { | ||
| 95 | if (ui->accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { | ||
| 96 | Settings::values.cpu_accuracy.SetGlobal(true); | ||
| 97 | } else { | ||
| 98 | Settings::values.cpu_accuracy.SetGlobal(false); | ||
| 99 | Settings::values.cpu_accuracy.SetValue(static_cast<Settings::CPUAccuracy>( | ||
| 100 | ui->accuracy->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); | ||
| 101 | } | ||
| 102 | } | ||
| 67 | } | 103 | } |
| 68 | 104 | ||
| 69 | void ConfigureCpu::changeEvent(QEvent* event) { | 105 | void ConfigureCpu::changeEvent(QEvent* event) { |
| @@ -77,3 +113,25 @@ void ConfigureCpu::changeEvent(QEvent* event) { | |||
| 77 | void ConfigureCpu::RetranslateUI() { | 113 | void ConfigureCpu::RetranslateUI() { |
| 78 | ui->retranslateUi(this); | 114 | ui->retranslateUi(this); |
| 79 | } | 115 | } |
| 116 | |||
| 117 | void ConfigureCpu::SetupPerGameUI() { | ||
| 118 | if (Settings::IsConfiguringGlobal()) { | ||
| 119 | return; | ||
| 120 | } | ||
| 121 | |||
| 122 | ConfigurationShared::SetColoredComboBox( | ||
| 123 | ui->accuracy, ui->widget_accuracy, | ||
| 124 | static_cast<u32>(Settings::values.cpu_accuracy.GetValue(true))); | ||
| 125 | ui->accuracy->removeItem(static_cast<u32>(Settings::CPUAccuracy::DebugMode) + | ||
| 126 | ConfigurationShared::USE_GLOBAL_OFFSET); | ||
| 127 | |||
| 128 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_unfuse_fma, | ||
| 129 | Settings::values.cpuopt_unsafe_unfuse_fma, | ||
| 130 | cpuopt_unsafe_unfuse_fma); | ||
| 131 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error, | ||
| 132 | Settings::values.cpuopt_unsafe_reduce_fp_error, | ||
| 133 | cpuopt_unsafe_reduce_fp_error); | ||
| 134 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, | ||
| 135 | Settings::values.cpuopt_unsafe_inaccurate_nan, | ||
| 136 | cpuopt_unsafe_inaccurate_nan); | ||
| 137 | } | ||
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index ef77b2e7e..8e2eeb7a6 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h | |||
| @@ -8,6 +8,10 @@ | |||
| 8 | #include <QWidget> | 8 | #include <QWidget> |
| 9 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 10 | 10 | ||
| 11 | namespace ConfigurationShared { | ||
| 12 | enum class CheckState; | ||
| 13 | } | ||
| 14 | |||
| 11 | namespace Ui { | 15 | namespace Ui { |
| 12 | class ConfigureCpu; | 16 | class ConfigureCpu; |
| 13 | } | 17 | } |
| @@ -30,5 +34,11 @@ private: | |||
| 30 | 34 | ||
| 31 | void SetConfiguration(); | 35 | void SetConfiguration(); |
| 32 | 36 | ||
| 37 | void SetupPerGameUI(); | ||
| 38 | |||
| 33 | std::unique_ptr<Ui::ConfigureCpu> ui; | 39 | std::unique_ptr<Ui::ConfigureCpu> ui; |
| 40 | |||
| 41 | ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; | ||
| 42 | ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; | ||
| 43 | ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; | ||
| 34 | }; | 44 | }; |
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index bcd0962e9..99b573640 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui | |||
| @@ -23,42 +23,44 @@ | |||
| 23 | </property> | 23 | </property> |
| 24 | <layout class="QVBoxLayout"> | 24 | <layout class="QVBoxLayout"> |
| 25 | <item> | 25 | <item> |
| 26 | <layout class="QHBoxLayout"> | 26 | <widget class="QWidget" name="widget_accuracy" native="true"> |
| 27 | <item> | 27 | <layout class="QHBoxLayout" name="layout_accuracy"> |
| 28 | <widget class="QLabel"> | 28 | <item> |
| 29 | <property name="text"> | 29 | <widget class="QLabel" name="label_accuracy"> |
| 30 | <string>Accuracy:</string> | ||
| 31 | </property> | ||
| 32 | </widget> | ||
| 33 | </item> | ||
| 34 | <item> | ||
| 35 | <widget class="QComboBox" name="accuracy"> | ||
| 36 | <item> | ||
| 37 | <property name="text"> | 30 | <property name="text"> |
| 38 | <string>Accurate</string> | 31 | <string>Accuracy:</string> |
| 39 | </property> | 32 | </property> |
| 40 | </item> | 33 | </widget> |
| 41 | <item> | 34 | </item> |
| 42 | <property name="text"> | 35 | <item> |
| 43 | <string>Unsafe</string> | 36 | <widget class="QComboBox" name="accuracy"> |
| 44 | </property> | 37 | <item> |
| 45 | </item> | 38 | <property name="text"> |
| 46 | <item> | 39 | <string>Accurate</string> |
| 47 | <property name="text"> | 40 | </property> |
| 48 | <string>Enable Debug Mode</string> | 41 | </item> |
| 49 | </property> | 42 | <item> |
| 50 | </item> | 43 | <property name="text"> |
| 51 | </widget> | 44 | <string>Unsafe</string> |
| 52 | </item> | 45 | </property> |
| 53 | </layout> | 46 | </item> |
| 47 | <item> | ||
| 48 | <property name="text"> | ||
| 49 | <string>Enable Debug Mode</string> | ||
| 50 | </property> | ||
| 51 | </item> | ||
| 52 | </widget> | ||
| 53 | </item> | ||
| 54 | </layout> | ||
| 55 | </widget> | ||
| 54 | </item> | 56 | </item> |
| 55 | <item> | 57 | <item> |
| 56 | <widget class="QLabel"> | 58 | <widget class="QLabel" name="label_recommended_accuracy"> |
| 57 | <property name="wordWrap"> | ||
| 58 | <bool>1</bool> | ||
| 59 | </property> | ||
| 60 | <property name="text"> | 59 | <property name="text"> |
| 61 | <string>We recommend setting accuracy to "Accurate".</string> | 60 | <string>We recommend setting accuracy to "Accurate".</string> |
| 61 | </property> | ||
| 62 | <property name="wordWrap"> | ||
| 63 | <bool>false</bool> | ||
| 62 | </property> | 64 | </property> |
| 63 | </widget> | 65 | </widget> |
| 64 | </item> | 66 | </item> |
| @@ -76,49 +78,49 @@ | |||
| 76 | </property> | 78 | </property> |
| 77 | <layout class="QVBoxLayout"> | 79 | <layout class="QVBoxLayout"> |
| 78 | <item> | 80 | <item> |
| 79 | <widget class="QLabel"> | 81 | <widget class="QLabel" name="label_accuracy_description"> |
| 80 | <property name="wordWrap"> | ||
| 81 | <bool>1</bool> | ||
| 82 | </property> | ||
| 83 | <property name="text"> | 82 | <property name="text"> |
| 84 | <string>These settings reduce accuracy for speed.</string> | 83 | <string>These settings reduce accuracy for speed.</string> |
| 85 | </property> | 84 | </property> |
| 85 | <property name="wordWrap"> | ||
| 86 | <bool>false</bool> | ||
| 87 | </property> | ||
| 86 | </widget> | 88 | </widget> |
| 87 | </item> | 89 | </item> |
| 88 | <item> | 90 | <item> |
| 89 | <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma"> | 91 | <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma"> |
| 90 | <property name="text"> | ||
| 91 | <string>Unfuse FMA (improve performance on CPUs without FMA)</string> | ||
| 92 | </property> | ||
| 93 | <property name="toolTip"> | 92 | <property name="toolTip"> |
| 94 | <string> | 93 | <string> |
| 95 | <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> | 94 | <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> |
| 96 | </string> | 95 | </string> |
| 97 | </property> | 96 | </property> |
| 97 | <property name="text"> | ||
| 98 | <string>Unfuse FMA (improve performance on CPUs without FMA)</string> | ||
| 99 | </property> | ||
| 98 | </widget> | 100 | </widget> |
| 99 | </item> | 101 | </item> |
| 100 | <item> | 102 | <item> |
| 101 | <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error"> | 103 | <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error"> |
| 102 | <property name="text"> | ||
| 103 | <string>Faster FRSQRTE and FRECPE</string> | ||
| 104 | </property> | ||
| 105 | <property name="toolTip"> | 104 | <property name="toolTip"> |
| 106 | <string> | 105 | <string> |
| 107 | <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> | 106 | <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> |
| 108 | </string> | 107 | </string> |
| 109 | </property> | 108 | </property> |
| 109 | <property name="text"> | ||
| 110 | <string>Faster FRSQRTE and FRECPE</string> | ||
| 111 | </property> | ||
| 110 | </widget> | 112 | </widget> |
| 111 | </item> | 113 | </item> |
| 112 | <item> | 114 | <item> |
| 113 | <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> | 115 | <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> |
| 114 | <property name="text"> | ||
| 115 | <string>Inaccurate NaN handling</string> | ||
| 116 | </property> | ||
| 117 | <property name="toolTip"> | 116 | <property name="toolTip"> |
| 118 | <string> | 117 | <string> |
| 119 | <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> | 118 | <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> |
| 120 | </string> | 119 | </string> |
| 121 | </property> | 120 | </property> |
| 121 | <property name="text"> | ||
| 122 | <string>Inaccurate NaN handling</string> | ||
| 123 | </property> | ||
| 122 | </widget> | 124 | </widget> |
| 123 | </item> | 125 | </item> |
| 124 | </layout> | 126 | </layout> |
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 6730eb356..b207e07cb 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include <QDesktopServices> | 5 | #include <QDesktopServices> |
| 6 | #include <QUrl> | 6 | #include <QUrl> |
| 7 | #include "common/file_util.h" | 7 | #include "common/fs/path_util.h" |
| 8 | #include "common/logging/backend.h" | 8 | #include "common/logging/backend.h" |
| 9 | #include "common/logging/filter.h" | 9 | #include "common/logging/filter.h" |
| 10 | #include "common/settings.h" | 10 | #include "common/settings.h" |
| @@ -20,7 +20,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co | |||
| 20 | 20 | ||
| 21 | connect(ui->open_log_button, &QPushButton::clicked, []() { | 21 | connect(ui->open_log_button, &QPushButton::clicked, []() { |
| 22 | const auto path = | 22 | const auto path = |
| 23 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir)); | 23 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir)); |
| 24 | QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | 24 | QDesktopServices::openUrl(QUrl::fromLocalFile(path)); |
| 25 | }); | 25 | }); |
| 26 | } | 26 | } |
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 006eda4b0..d223c40ea 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp | |||
| @@ -4,8 +4,8 @@ | |||
| 4 | 4 | ||
| 5 | #include <QFileDialog> | 5 | #include <QFileDialog> |
| 6 | #include <QMessageBox> | 6 | #include <QMessageBox> |
| 7 | #include "common/common_paths.h" | 7 | #include "common/fs/fs.h" |
| 8 | #include "common/file_util.h" | 8 | #include "common/fs/path_util.h" |
| 9 | #include "common/settings.h" | 9 | #include "common/settings.h" |
| 10 | #include "ui_configure_filesystem.h" | 10 | #include "ui_configure_filesystem.h" |
| 11 | #include "yuzu/configuration/configure_filesystem.h" | 11 | #include "yuzu/configuration/configure_filesystem.h" |
| @@ -40,14 +40,14 @@ ConfigureFilesystem::~ConfigureFilesystem() = default; | |||
| 40 | 40 | ||
| 41 | void ConfigureFilesystem::setConfiguration() { | 41 | void ConfigureFilesystem::setConfiguration() { |
| 42 | ui->nand_directory_edit->setText( | 42 | ui->nand_directory_edit->setText( |
| 43 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir))); | 43 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir))); |
| 44 | ui->sdmc_directory_edit->setText( | 44 | ui->sdmc_directory_edit->setText( |
| 45 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir))); | 45 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::SDMCDir))); |
| 46 | ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); | 46 | ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); |
| 47 | ui->dump_path_edit->setText( | 47 | ui->dump_path_edit->setText( |
| 48 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); | 48 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir))); |
| 49 | ui->load_path_edit->setText( | 49 | ui->load_path_edit->setText( |
| 50 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); | 50 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir))); |
| 51 | 51 | ||
| 52 | ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); | 52 | ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); |
| 53 | ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); | 53 | ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); |
| @@ -60,13 +60,13 @@ void ConfigureFilesystem::setConfiguration() { | |||
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | void ConfigureFilesystem::applyConfiguration() { | 62 | void ConfigureFilesystem::applyConfiguration() { |
| 63 | Common::FS::GetUserPath(Common::FS::UserPath::NANDDir, | 63 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::NANDDir, |
| 64 | ui->nand_directory_edit->text().toStdString()); | 64 | ui->nand_directory_edit->text().toStdString()); |
| 65 | Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir, | 65 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::SDMCDir, |
| 66 | ui->sdmc_directory_edit->text().toStdString()); | 66 | ui->sdmc_directory_edit->text().toStdString()); |
| 67 | Common::FS::GetUserPath(Common::FS::UserPath::DumpDir, | 67 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::DumpDir, |
| 68 | ui->dump_path_edit->text().toStdString()); | 68 | ui->dump_path_edit->text().toStdString()); |
| 69 | Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, | 69 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir, |
| 70 | ui->load_path_edit->text().toStdString()); | 70 | ui->load_path_edit->text().toStdString()); |
| 71 | 71 | ||
| 72 | Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); | 72 | Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); |
| @@ -104,25 +104,26 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) | |||
| 104 | QStringLiteral("NX Gamecard;*.xci")); | 104 | QStringLiteral("NX Gamecard;*.xci")); |
| 105 | } else { | 105 | } else { |
| 106 | str = QFileDialog::getExistingDirectory(this, caption, edit->text()); | 106 | str = QFileDialog::getExistingDirectory(this, caption, edit->text()); |
| 107 | if (!str.isNull() && str.back() != QDir::separator()) { | ||
| 108 | str.append(QDir::separator()); | ||
| 109 | } | ||
| 110 | } | 107 | } |
| 111 | 108 | ||
| 112 | if (str.isEmpty()) | 109 | if (str.isNull() || str.isEmpty()) { |
| 113 | return; | 110 | return; |
| 111 | } | ||
| 112 | |||
| 113 | if (str.back() != QChar::fromLatin1('/')) { | ||
| 114 | str.append(QChar::fromLatin1('/')); | ||
| 115 | } | ||
| 114 | 116 | ||
| 115 | edit->setText(str); | 117 | edit->setText(str); |
| 116 | } | 118 | } |
| 117 | 119 | ||
| 118 | void ConfigureFilesystem::ResetMetadata() { | 120 | void ConfigureFilesystem::ResetMetadata() { |
| 119 | if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 121 | if (!Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 120 | "game_list")) { | 122 | "game_list/")) { |
| 121 | QMessageBox::information(this, tr("Reset Metadata Cache"), | 123 | QMessageBox::information(this, tr("Reset Metadata Cache"), |
| 122 | tr("The metadata cache is already empty.")); | 124 | tr("The metadata cache is already empty.")); |
| 123 | } else if (Common::FS::DeleteDirRecursively( | 125 | } else if (Common::FS::RemoveDirRecursively( |
| 124 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 126 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list")) { |
| 125 | "game_list")) { | ||
| 126 | QMessageBox::information(this, tr("Reset Metadata Cache"), | 127 | QMessageBox::information(this, tr("Reset Metadata Cache"), |
| 127 | tr("The operation completed successfully.")); | 128 | tr("The operation completed successfully.")); |
| 128 | UISettings::values.is_game_list_reload_pending.exchange(true); | 129 | UISettings::values.is_game_list_reload_pending.exchange(true); |
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 2fa88dcec..55a6a37bd 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp | |||
| @@ -50,6 +50,9 @@ void ConfigureGeneral::SetConfiguration() { | |||
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | void ConfigureGeneral::ApplyConfiguration() { | 52 | void ConfigureGeneral::ApplyConfiguration() { |
| 53 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, | ||
| 54 | use_multi_core); | ||
| 55 | |||
| 53 | if (Settings::IsConfiguringGlobal()) { | 56 | if (Settings::IsConfiguringGlobal()) { |
| 54 | UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); | 57 | UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); |
| 55 | UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); | 58 | UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); |
| @@ -62,13 +65,7 @@ void ConfigureGeneral::ApplyConfiguration() { | |||
| 62 | Qt::Checked); | 65 | Qt::Checked); |
| 63 | Settings::values.frame_limit.SetValue(ui->frame_limit->value()); | 66 | Settings::values.frame_limit.SetValue(ui->frame_limit->value()); |
| 64 | } | 67 | } |
| 65 | if (Settings::values.use_multi_core.UsingGlobal()) { | ||
| 66 | Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); | ||
| 67 | } | ||
| 68 | } else { | 68 | } else { |
| 69 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, | ||
| 70 | ui->use_multi_core, use_multi_core); | ||
| 71 | |||
| 72 | bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global; | 69 | bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global; |
| 73 | Settings::values.use_frame_limit.SetGlobal(global_frame_limit); | 70 | Settings::values.use_frame_limit.SetGlobal(global_frame_limit); |
| 74 | Settings::values.frame_limit.SetGlobal(global_frame_limit); | 71 | Settings::values.frame_limit.SetGlobal(global_frame_limit); |
| @@ -94,6 +91,9 @@ void ConfigureGeneral::RetranslateUI() { | |||
| 94 | 91 | ||
| 95 | void ConfigureGeneral::SetupPerGameUI() { | 92 | void ConfigureGeneral::SetupPerGameUI() { |
| 96 | if (Settings::IsConfiguringGlobal()) { | 93 | if (Settings::IsConfiguringGlobal()) { |
| 94 | // Disables each setting if: | ||
| 95 | // - A game is running (thus settings in use), and | ||
| 96 | // - A non-global setting is applied. | ||
| 97 | ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); | 97 | ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); |
| 98 | ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); | 98 | ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); |
| 99 | 99 | ||
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 0a7536617..fb9ec093c 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp | |||
| @@ -106,6 +106,19 @@ void ConfigureGraphics::SetConfiguration() { | |||
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | void ConfigureGraphics::ApplyConfiguration() { | 108 | void ConfigureGraphics::ApplyConfiguration() { |
| 109 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, | ||
| 110 | ui->fullscreen_mode_combobox); | ||
| 111 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, | ||
| 112 | ui->aspect_ratio_combobox); | ||
| 113 | |||
| 114 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, | ||
| 115 | ui->use_disk_shader_cache, use_disk_shader_cache); | ||
| 116 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, | ||
| 117 | ui->use_asynchronous_gpu_emulation, | ||
| 118 | use_asynchronous_gpu_emulation); | ||
| 119 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, | ||
| 120 | ui->use_nvdec_emulation, use_nvdec_emulation); | ||
| 121 | |||
| 109 | if (Settings::IsConfiguringGlobal()) { | 122 | if (Settings::IsConfiguringGlobal()) { |
| 110 | // Guard if during game and set to game-specific value | 123 | // Guard if during game and set to game-specific value |
| 111 | if (Settings::values.renderer_backend.UsingGlobal()) { | 124 | if (Settings::values.renderer_backend.UsingGlobal()) { |
| @@ -114,22 +127,6 @@ void ConfigureGraphics::ApplyConfiguration() { | |||
| 114 | if (Settings::values.vulkan_device.UsingGlobal()) { | 127 | if (Settings::values.vulkan_device.UsingGlobal()) { |
| 115 | Settings::values.vulkan_device.SetValue(vulkan_device); | 128 | Settings::values.vulkan_device.SetValue(vulkan_device); |
| 116 | } | 129 | } |
| 117 | if (Settings::values.fullscreen_mode.UsingGlobal()) { | ||
| 118 | Settings::values.fullscreen_mode.SetValue(ui->fullscreen_mode_combobox->currentIndex()); | ||
| 119 | } | ||
| 120 | if (Settings::values.aspect_ratio.UsingGlobal()) { | ||
| 121 | Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); | ||
| 122 | } | ||
| 123 | if (Settings::values.use_disk_shader_cache.UsingGlobal()) { | ||
| 124 | Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked()); | ||
| 125 | } | ||
| 126 | if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) { | ||
| 127 | Settings::values.use_asynchronous_gpu_emulation.SetValue( | ||
| 128 | ui->use_asynchronous_gpu_emulation->isChecked()); | ||
| 129 | } | ||
| 130 | if (Settings::values.use_nvdec_emulation.UsingGlobal()) { | ||
| 131 | Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked()); | ||
| 132 | } | ||
| 133 | if (Settings::values.bg_red.UsingGlobal()) { | 130 | if (Settings::values.bg_red.UsingGlobal()) { |
| 134 | Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); | 131 | Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); |
| 135 | Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); | 132 | Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); |
| @@ -150,19 +147,6 @@ void ConfigureGraphics::ApplyConfiguration() { | |||
| 150 | } | 147 | } |
| 151 | } | 148 | } |
| 152 | 149 | ||
| 153 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, | ||
| 154 | ui->fullscreen_mode_combobox); | ||
| 155 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, | ||
| 156 | ui->aspect_ratio_combobox); | ||
| 157 | |||
| 158 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, | ||
| 159 | ui->use_disk_shader_cache, use_disk_shader_cache); | ||
| 160 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, | ||
| 161 | ui->use_asynchronous_gpu_emulation, | ||
| 162 | use_asynchronous_gpu_emulation); | ||
| 163 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, | ||
| 164 | ui->use_nvdec_emulation, use_nvdec_emulation); | ||
| 165 | |||
| 166 | if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { | 150 | if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { |
| 167 | Settings::values.bg_red.SetGlobal(true); | 151 | Settings::values.bg_red.SetGlobal(true); |
| 168 | Settings::values.bg_green.SetGlobal(true); | 152 | Settings::values.bg_green.SetGlobal(true); |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index c67609b0e..35bf9c6be 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp | |||
| @@ -54,47 +54,23 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { | |||
| 54 | ui->gpu_accuracy->currentIndex() - | 54 | ui->gpu_accuracy->currentIndex() - |
| 55 | ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); | 55 | ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); |
| 56 | 56 | ||
| 57 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, | ||
| 58 | ui->anisotropic_filtering_combobox); | ||
| 59 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); | ||
| 60 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, | ||
| 61 | ui->use_assembly_shaders, use_assembly_shaders); | ||
| 62 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, | ||
| 63 | ui->use_asynchronous_shaders, | ||
| 64 | use_asynchronous_shaders); | ||
| 65 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, | ||
| 66 | ui->use_fast_gpu_time, use_fast_gpu_time); | ||
| 67 | |||
| 57 | if (Settings::IsConfiguringGlobal()) { | 68 | if (Settings::IsConfiguringGlobal()) { |
| 58 | // Must guard in case of a during-game configuration when set to be game-specific. | 69 | // Must guard in case of a during-game configuration when set to be game-specific. |
| 59 | if (Settings::values.gpu_accuracy.UsingGlobal()) { | 70 | if (Settings::values.gpu_accuracy.UsingGlobal()) { |
| 60 | Settings::values.gpu_accuracy.SetValue(gpu_accuracy); | 71 | Settings::values.gpu_accuracy.SetValue(gpu_accuracy); |
| 61 | } | 72 | } |
| 62 | if (Settings::values.use_vsync.UsingGlobal()) { | ||
| 63 | Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked()); | ||
| 64 | } | ||
| 65 | if (Settings::values.use_assembly_shaders.UsingGlobal()) { | ||
| 66 | Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); | ||
| 67 | } | ||
| 68 | if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { | ||
| 69 | Settings::values.use_asynchronous_shaders.SetValue( | ||
| 70 | ui->use_asynchronous_shaders->isChecked()); | ||
| 71 | } | ||
| 72 | if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { | ||
| 73 | Settings::values.use_asynchronous_shaders.SetValue( | ||
| 74 | ui->use_asynchronous_shaders->isChecked()); | ||
| 75 | } | ||
| 76 | if (Settings::values.use_fast_gpu_time.UsingGlobal()) { | ||
| 77 | Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); | ||
| 78 | } | ||
| 79 | if (Settings::values.max_anisotropy.UsingGlobal()) { | ||
| 80 | Settings::values.max_anisotropy.SetValue( | ||
| 81 | ui->anisotropic_filtering_combobox->currentIndex()); | ||
| 82 | } | ||
| 83 | } else { | 73 | } else { |
| 84 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, | ||
| 85 | ui->anisotropic_filtering_combobox); | ||
| 86 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, | ||
| 87 | use_vsync); | ||
| 88 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, | ||
| 89 | ui->use_assembly_shaders, use_assembly_shaders); | ||
| 90 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, | ||
| 91 | ui->use_asynchronous_shaders, | ||
| 92 | use_asynchronous_shaders); | ||
| 93 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, | ||
| 94 | ui->use_fast_gpu_time, use_fast_gpu_time); | ||
| 95 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, | ||
| 96 | ui->anisotropic_filtering_combobox); | ||
| 97 | |||
| 98 | if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { | 74 | if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { |
| 99 | Settings::values.gpu_accuracy.SetGlobal(true); | 75 | Settings::values.gpu_accuracy.SetGlobal(true); |
| 100 | } else { | 76 | } else { |
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index bd91ebc42..3e13bd438 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp | |||
| @@ -14,8 +14,6 @@ | |||
| 14 | #include <QTimer> | 14 | #include <QTimer> |
| 15 | #include <QTreeView> | 15 | #include <QTreeView> |
| 16 | 16 | ||
| 17 | #include "common/common_paths.h" | ||
| 18 | #include "common/file_util.h" | ||
| 19 | #include "core/core.h" | 17 | #include "core/core.h" |
| 20 | #include "core/file_sys/control_metadata.h" | 18 | #include "core/file_sys/control_metadata.h" |
| 21 | #include "core/file_sys/patch_manager.h" | 19 | #include "core/file_sys/patch_manager.h" |
| @@ -52,6 +50,7 @@ ConfigurePerGame::~ConfigurePerGame() = default; | |||
| 52 | void ConfigurePerGame::ApplyConfiguration() { | 50 | void ConfigurePerGame::ApplyConfiguration() { |
| 53 | ui->addonsTab->ApplyConfiguration(); | 51 | ui->addonsTab->ApplyConfiguration(); |
| 54 | ui->generalTab->ApplyConfiguration(); | 52 | ui->generalTab->ApplyConfiguration(); |
| 53 | ui->cpuTab->ApplyConfiguration(); | ||
| 55 | ui->systemTab->ApplyConfiguration(); | 54 | ui->systemTab->ApplyConfiguration(); |
| 56 | ui->graphicsTab->ApplyConfiguration(); | 55 | ui->graphicsTab->ApplyConfiguration(); |
| 57 | ui->graphicsAdvancedTab->ApplyConfiguration(); | 56 | ui->graphicsAdvancedTab->ApplyConfiguration(); |
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index 25975b3b9..adf6d0b39 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/src/yuzu/configuration/configure_per_game.ui | |||
| @@ -235,6 +235,11 @@ | |||
| 235 | <string>System</string> | 235 | <string>System</string> |
| 236 | </attribute> | 236 | </attribute> |
| 237 | </widget> | 237 | </widget> |
| 238 | <widget class="ConfigureCpu" name="cpuTab"> | ||
| 239 | <attribute name="title"> | ||
| 240 | <string>CPU</string> | ||
| 241 | </attribute> | ||
| 242 | </widget> | ||
| 238 | <widget class="ConfigureGraphics" name="graphicsTab"> | 243 | <widget class="ConfigureGraphics" name="graphicsTab"> |
| 239 | <attribute name="title"> | 244 | <attribute name="title"> |
| 240 | <string>Graphics</string> | 245 | <string>Graphics</string> |
| @@ -311,6 +316,12 @@ | |||
| 311 | <header>configuration/configure_per_game_addons.h</header> | 316 | <header>configuration/configure_per_game_addons.h</header> |
| 312 | <container>1</container> | 317 | <container>1</container> |
| 313 | </customwidget> | 318 | </customwidget> |
| 319 | <customwidget> | ||
| 320 | <class>ConfigureCpu</class> | ||
| 321 | <extends>QWidget</extends> | ||
| 322 | <header>configuration/configure_cpu.h</header> | ||
| 323 | <container>1</container> | ||
| 324 | </customwidget> | ||
| 314 | </customwidgets> | 325 | </customwidgets> |
| 315 | <resources/> | 326 | <resources/> |
| 316 | <connections> | 327 | <connections> |
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index cdeeec01c..9b709d405 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp | |||
| @@ -13,8 +13,8 @@ | |||
| 13 | #include <QTimer> | 13 | #include <QTimer> |
| 14 | #include <QTreeView> | 14 | #include <QTreeView> |
| 15 | 15 | ||
| 16 | #include "common/common_paths.h" | 16 | #include "common/fs/fs.h" |
| 17 | #include "common/file_util.h" | 17 | #include "common/fs/path_util.h" |
| 18 | #include "core/core.h" | 18 | #include "core/core.h" |
| 19 | #include "core/file_sys/patch_manager.h" | 19 | #include "core/file_sys/patch_manager.h" |
| 20 | #include "core/file_sys/xts_archive.h" | 20 | #include "core/file_sys/xts_archive.h" |
| @@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { | |||
| 79 | std::sort(disabled_addons.begin(), disabled_addons.end()); | 79 | std::sort(disabled_addons.begin(), disabled_addons.end()); |
| 80 | std::sort(current.begin(), current.end()); | 80 | std::sort(current.begin(), current.end()); |
| 81 | if (disabled_addons != current) { | 81 | if (disabled_addons != current) { |
| 82 | Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 82 | void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 83 | "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); | 83 | "game_list" / fmt::format("{:016X}.pv.txt", title_id))); |
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | Settings::values.disabled_addons[title_id] = disabled_addons; | 86 | Settings::values.disabled_addons[title_id] = disabled_addons; |
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index d61b5e29b..f5881e58d 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp | |||
| @@ -12,7 +12,7 @@ | |||
| 12 | #include <QTreeView> | 12 | #include <QTreeView> |
| 13 | #include <QVBoxLayout> | 13 | #include <QVBoxLayout> |
| 14 | #include "common/assert.h" | 14 | #include "common/assert.h" |
| 15 | #include "common/file_util.h" | 15 | #include "common/fs/path_util.h" |
| 16 | #include "common/settings.h" | 16 | #include "common/settings.h" |
| 17 | #include "common/string_util.h" | 17 | #include "common/string_util.h" |
| 18 | #include "core/core.h" | 18 | #include "core/core.h" |
| @@ -34,9 +34,10 @@ constexpr std::array<u8, 107> backup_jpeg{ | |||
| 34 | }; | 34 | }; |
| 35 | 35 | ||
| 36 | QString GetImagePath(Common::UUID uuid) { | 36 | QString GetImagePath(Common::UUID uuid) { |
| 37 | const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 37 | const auto path = |
| 38 | "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; | 38 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / |
| 39 | return QString::fromStdString(path); | 39 | fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); |
| 40 | return QString::fromStdString(Common::FS::PathToUTF8String(path)); | ||
| 40 | } | 41 | } |
| 41 | 42 | ||
| 42 | QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) { | 43 | QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) { |
| @@ -281,8 +282,8 @@ void ConfigureProfileManager::SetUserImage() { | |||
| 281 | return; | 282 | return; |
| 282 | } | 283 | } |
| 283 | 284 | ||
| 284 | const auto raw_path = QString::fromStdString( | 285 | const auto raw_path = QString::fromStdString(Common::FS::PathToUTF8String( |
| 285 | Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010"); | 286 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000010")); |
| 286 | const QFileInfo raw_info{raw_path}; | 287 | const QFileInfo raw_info{raw_path}; |
| 287 | if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { | 288 | if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { |
| 288 | QMessageBox::warning(this, tr("Error deleting file"), | 289 | QMessageBox::warning(this, tr("Error deleting file"), |
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 268ed44c3..99a5df241 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp | |||
| @@ -10,7 +10,6 @@ | |||
| 10 | #include <QGraphicsItem> | 10 | #include <QGraphicsItem> |
| 11 | #include <QMessageBox> | 11 | #include <QMessageBox> |
| 12 | #include "common/assert.h" | 12 | #include "common/assert.h" |
| 13 | #include "common/file_util.h" | ||
| 14 | #include "common/settings.h" | 13 | #include "common/settings.h" |
| 15 | #include "core/core.h" | 14 | #include "core/core.h" |
| 16 | #include "core/hle/service/time/time.h" | 15 | #include "core/hle/service/time/time.h" |
| @@ -65,7 +64,7 @@ void ConfigureSystem::SetConfiguration() { | |||
| 65 | QStringLiteral("%1") | 64 | QStringLiteral("%1") |
| 66 | .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) | 65 | .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) |
| 67 | .toUpper(); | 66 | .toUpper(); |
| 68 | const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( | 67 | const auto rtc_time = Settings::values.custom_rtc.value_or( |
| 69 | std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); | 68 | std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); |
| 70 | 69 | ||
| 71 | ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); | 70 | ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); |
| @@ -73,9 +72,8 @@ void ConfigureSystem::SetConfiguration() { | |||
| 73 | Settings::values.rng_seed.UsingGlobal()); | 72 | Settings::values.rng_seed.UsingGlobal()); |
| 74 | ui->rng_seed_edit->setText(rng_seed); | 73 | ui->rng_seed_edit->setText(rng_seed); |
| 75 | 74 | ||
| 76 | ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); | 75 | ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); |
| 77 | ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && | 76 | ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); |
| 78 | Settings::values.rng_seed.UsingGlobal()); | ||
| 79 | ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); | 77 | ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); |
| 80 | 78 | ||
| 81 | if (Settings::IsConfiguringGlobal()) { | 79 | if (Settings::IsConfiguringGlobal()) { |
| @@ -109,17 +107,17 @@ void ConfigureSystem::ApplyConfiguration() { | |||
| 109 | 107 | ||
| 110 | // Allow setting custom RTC even if system is powered on, | 108 | // Allow setting custom RTC even if system is powered on, |
| 111 | // to allow in-game time to be fast forwarded | 109 | // to allow in-game time to be fast forwarded |
| 112 | if (Settings::values.custom_rtc.UsingGlobal()) { | 110 | if (Settings::IsConfiguringGlobal()) { |
| 113 | if (ui->custom_rtc_checkbox->isChecked()) { | 111 | if (ui->custom_rtc_checkbox->isChecked()) { |
| 114 | Settings::values.custom_rtc.SetValue( | 112 | Settings::values.custom_rtc = |
| 115 | std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); | 113 | std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); |
| 116 | if (system.IsPoweredOn()) { | 114 | if (system.IsPoweredOn()) { |
| 117 | const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() + | 115 | const s64 posix_time{Settings::values.custom_rtc->count() + |
| 118 | Service::Time::TimeManager::GetExternalTimeZoneOffset()}; | 116 | Service::Time::TimeManager::GetExternalTimeZoneOffset()}; |
| 119 | system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); | 117 | system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); |
| 120 | } | 118 | } |
| 121 | } else { | 119 | } else { |
| 122 | Settings::values.custom_rtc.SetValue(std::nullopt); | 120 | Settings::values.custom_rtc = std::nullopt; |
| 123 | } | 121 | } |
| 124 | } | 122 | } |
| 125 | 123 | ||
| @@ -127,21 +125,14 @@ void ConfigureSystem::ApplyConfiguration() { | |||
| 127 | return; | 125 | return; |
| 128 | } | 126 | } |
| 129 | 127 | ||
| 128 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language); | ||
| 129 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); | ||
| 130 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, | ||
| 131 | ui->combo_time_zone); | ||
| 132 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); | ||
| 133 | |||
| 130 | if (Settings::IsConfiguringGlobal()) { | 134 | if (Settings::IsConfiguringGlobal()) { |
| 131 | // Guard if during game and set to game-specific value | 135 | // Guard if during game and set to game-specific value |
| 132 | if (Settings::values.language_index.UsingGlobal()) { | ||
| 133 | Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); | ||
| 134 | } | ||
| 135 | if (Settings::values.region_index.UsingGlobal()) { | ||
| 136 | Settings::values.region_index.SetValue(ui->combo_region->currentIndex()); | ||
| 137 | } | ||
| 138 | if (Settings::values.time_zone_index.UsingGlobal()) { | ||
| 139 | Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex()); | ||
| 140 | } | ||
| 141 | if (Settings::values.sound_index.UsingGlobal()) { | ||
| 142 | Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex()); | ||
| 143 | } | ||
| 144 | |||
| 145 | if (Settings::values.rng_seed.UsingGlobal()) { | 136 | if (Settings::values.rng_seed.UsingGlobal()) { |
| 146 | if (ui->rng_seed_checkbox->isChecked()) { | 137 | if (ui->rng_seed_checkbox->isChecked()) { |
| 147 | Settings::values.rng_seed.SetValue( | 138 | Settings::values.rng_seed.SetValue( |
| @@ -151,13 +142,6 @@ void ConfigureSystem::ApplyConfiguration() { | |||
| 151 | } | 142 | } |
| 152 | } | 143 | } |
| 153 | } else { | 144 | } else { |
| 154 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, | ||
| 155 | ui->combo_language); | ||
| 156 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); | ||
| 157 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, | ||
| 158 | ui->combo_time_zone); | ||
| 159 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); | ||
| 160 | |||
| 161 | switch (use_rng_seed) { | 145 | switch (use_rng_seed) { |
| 162 | case ConfigurationShared::CheckState::On: | 146 | case ConfigurationShared::CheckState::On: |
| 163 | case ConfigurationShared::CheckState::Off: | 147 | case ConfigurationShared::CheckState::Off: |
| @@ -177,26 +161,6 @@ void ConfigureSystem::ApplyConfiguration() { | |||
| 177 | case ConfigurationShared::CheckState::Count: | 161 | case ConfigurationShared::CheckState::Count: |
| 178 | break; | 162 | break; |
| 179 | } | 163 | } |
| 180 | |||
| 181 | switch (use_custom_rtc) { | ||
| 182 | case ConfigurationShared::CheckState::On: | ||
| 183 | case ConfigurationShared::CheckState::Off: | ||
| 184 | Settings::values.custom_rtc.SetGlobal(false); | ||
| 185 | if (ui->custom_rtc_checkbox->isChecked()) { | ||
| 186 | Settings::values.custom_rtc.SetValue( | ||
| 187 | std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); | ||
| 188 | } else { | ||
| 189 | Settings::values.custom_rtc.SetValue(std::nullopt); | ||
| 190 | } | ||
| 191 | break; | ||
| 192 | case ConfigurationShared::CheckState::Global: | ||
| 193 | Settings::values.custom_rtc.SetGlobal(false); | ||
| 194 | Settings::values.custom_rtc.SetValue(std::nullopt); | ||
| 195 | Settings::values.custom_rtc.SetGlobal(true); | ||
| 196 | break; | ||
| 197 | case ConfigurationShared::CheckState::Count: | ||
| 198 | break; | ||
| 199 | } | ||
| 200 | } | 164 | } |
| 201 | 165 | ||
| 202 | system.ApplySettings(); | 166 | system.ApplySettings(); |
| @@ -227,8 +191,6 @@ void ConfigureSystem::SetupPerGameUI() { | |||
| 227 | ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); | 191 | ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); |
| 228 | ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); | 192 | ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); |
| 229 | ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); | 193 | ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); |
| 230 | ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal()); | ||
| 231 | ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal()); | ||
| 232 | 194 | ||
| 233 | return; | 195 | return; |
| 234 | } | 196 | } |
| @@ -246,8 +208,7 @@ void ConfigureSystem::SetupPerGameUI() { | |||
| 246 | ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), | 208 | ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), |
| 247 | Settings::values.rng_seed.GetValue().has_value(), | 209 | Settings::values.rng_seed.GetValue().has_value(), |
| 248 | Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); | 210 | Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); |
| 249 | ConfigurationShared::SetColoredTristate( | 211 | |
| 250 | ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(), | 212 | ui->custom_rtc_checkbox->setVisible(false); |
| 251 | Settings::values.custom_rtc.GetValue().has_value(), | 213 | ui->custom_rtc_edit->setVisible(false); |
| 252 | Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc); | ||
| 253 | } | 214 | } |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 0cdaea8a4..0a28c87c0 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -8,7 +8,7 @@ | |||
| 8 | 8 | ||
| 9 | #include <QDirIterator> | 9 | #include <QDirIterator> |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "common/file_util.h" | 11 | #include "common/fs/path_util.h" |
| 12 | #include "common/settings.h" | 12 | #include "common/settings.h" |
| 13 | #include "core/core.h" | 13 | #include "core/core.h" |
| 14 | #include "ui_configure_ui.h" | 14 | #include "ui_configure_ui.h" |
| @@ -62,13 +62,16 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur | |||
| 62 | 62 | ||
| 63 | // Set screenshot path to user specification. | 63 | // Set screenshot path to user specification. |
| 64 | connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { | 64 | connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { |
| 65 | const QString& filename = | 65 | auto dir = |
| 66 | QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), | 66 | QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), |
| 67 | QString::fromStdString(Common::FS::GetUserPath( | 67 | QString::fromStdString(Common::FS::GetYuzuPathString( |
| 68 | Common::FS::UserPath::ScreenshotsDir))) + | 68 | Common::FS::YuzuPath::ScreenshotsDir))); |
| 69 | QDir::separator(); | 69 | if (!dir.isEmpty()) { |
| 70 | if (!filename.isEmpty()) { | 70 | if (dir.back() != QChar::fromLatin1('/')) { |
| 71 | ui->screenshot_path_edit->setText(filename); | 71 | dir.append(QChar::fromLatin1('/')); |
| 72 | } | ||
| 73 | |||
| 74 | ui->screenshot_path_edit->setText(dir); | ||
| 72 | } | 75 | } |
| 73 | }); | 76 | }); |
| 74 | } | 77 | } |
| @@ -84,7 +87,7 @@ void ConfigureUi::ApplyConfiguration() { | |||
| 84 | UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); | 87 | UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); |
| 85 | 88 | ||
| 86 | UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); | 89 | UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); |
| 87 | Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, | 90 | Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir, |
| 88 | ui->screenshot_path_edit->text().toStdString()); | 91 | ui->screenshot_path_edit->text().toStdString()); |
| 89 | Core::System::GetInstance().ApplySettings(); | 92 | Core::System::GetInstance().ApplySettings(); |
| 90 | } | 93 | } |
| @@ -102,8 +105,8 @@ void ConfigureUi::SetConfiguration() { | |||
| 102 | ui->icon_size_combobox->findData(UISettings::values.icon_size)); | 105 | ui->icon_size_combobox->findData(UISettings::values.icon_size)); |
| 103 | 106 | ||
| 104 | ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); | 107 | ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); |
| 105 | ui->screenshot_path_edit->setText( | 108 | ui->screenshot_path_edit->setText(QString::fromStdString( |
| 106 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir))); | 109 | Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir))); |
| 107 | } | 110 | } |
| 108 | 111 | ||
| 109 | void ConfigureUi::changeEvent(QEvent* event) { | 112 | void ConfigureUi::changeEvent(QEvent* event) { |
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index e87aededb..333eeb84e 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp | |||
| @@ -4,8 +4,8 @@ | |||
| 4 | 4 | ||
| 5 | #include <fmt/format.h> | 5 | #include <fmt/format.h> |
| 6 | 6 | ||
| 7 | #include "common/common_paths.h" | 7 | #include "common/fs/fs.h" |
| 8 | #include "common/file_util.h" | 8 | #include "common/fs/path_util.h" |
| 9 | #include "yuzu/configuration/config.h" | 9 | #include "yuzu/configuration/config.h" |
| 10 | #include "yuzu/configuration/input_profiles.h" | 10 | #include "yuzu/configuration/input_profiles.h" |
| 11 | 11 | ||
| @@ -14,47 +14,43 @@ namespace FS = Common::FS; | |||
| 14 | namespace { | 14 | namespace { |
| 15 | 15 | ||
| 16 | bool ProfileExistsInFilesystem(std::string_view profile_name) { | 16 | bool ProfileExistsInFilesystem(std::string_view profile_name) { |
| 17 | return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", | 17 | return FS::Exists(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input" / |
| 18 | FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); | 18 | fmt::format("{}.ini", profile_name)); |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | bool IsINI(std::string_view filename) { | 21 | bool IsINI(const std::filesystem::path& filename) { |
| 22 | const std::size_t index = filename.rfind('.'); | 22 | return filename.extension() == ".ini"; |
| 23 | |||
| 24 | if (index == std::string::npos) { | ||
| 25 | return false; | ||
| 26 | } | ||
| 27 | |||
| 28 | return filename.substr(index) == ".ini"; | ||
| 29 | } | 23 | } |
| 30 | 24 | ||
| 31 | std::string GetNameWithoutExtension(const std::string& filename) { | 25 | std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { |
| 32 | const std::size_t index = filename.rfind('.'); | 26 | return filename.replace_extension(); |
| 33 | |||
| 34 | if (index == std::string::npos) { | ||
| 35 | return filename; | ||
| 36 | } | ||
| 37 | |||
| 38 | return filename.substr(0, index); | ||
| 39 | } | 27 | } |
| 40 | 28 | ||
| 41 | } // namespace | 29 | } // namespace |
| 42 | 30 | ||
| 43 | InputProfiles::InputProfiles() { | 31 | InputProfiles::InputProfiles() { |
| 44 | const std::string input_profile_loc = | 32 | const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; |
| 45 | fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); | 33 | |
| 34 | if (!FS::IsDir(input_profile_loc)) { | ||
| 35 | return; | ||
| 36 | } | ||
| 46 | 37 | ||
| 47 | FS::ForeachDirectoryEntry( | 38 | FS::IterateDirEntries( |
| 48 | nullptr, input_profile_loc, | 39 | input_profile_loc, |
| 49 | [this](u64* entries_out, const std::string& directory, const std::string& filename) { | 40 | [this](const std::filesystem::path& full_path) { |
| 50 | if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { | 41 | const auto filename = full_path.filename(); |
| 42 | const auto name_without_ext = | ||
| 43 | Common::FS::PathToUTF8String(GetNameWithoutExtension(filename)); | ||
| 44 | |||
| 45 | if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { | ||
| 51 | map_profiles.insert_or_assign( | 46 | map_profiles.insert_or_assign( |
| 52 | GetNameWithoutExtension(filename), | 47 | name_without_ext, |
| 53 | std::make_unique<Config>(GetNameWithoutExtension(filename), | 48 | std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile)); |
| 54 | Config::ConfigType::InputProfile)); | ||
| 55 | } | 49 | } |
| 50 | |||
| 56 | return true; | 51 | return true; |
| 57 | }); | 52 | }, |
| 53 | FS::DirEntryFilter::File); | ||
| 58 | } | 54 | } |
| 59 | 55 | ||
| 60 | InputProfiles::~InputProfiles() = default; | 56 | InputProfiles::~InputProfiles() = default; |
| @@ -96,7 +92,7 @@ bool InputProfiles::DeleteProfile(const std::string& profile_name) { | |||
| 96 | } | 92 | } |
| 97 | 93 | ||
| 98 | if (!ProfileExistsInFilesystem(profile_name) || | 94 | if (!ProfileExistsInFilesystem(profile_name) || |
| 99 | FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { | 95 | FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) { |
| 100 | map_profiles.erase(profile_name); | 96 | map_profiles.erase(profile_name); |
| 101 | } | 97 | } |
| 102 | 98 | ||
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 7186eac76..d85408ac6 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp | |||
| @@ -38,6 +38,7 @@ void ControllerDialog::refreshConfiguration() { | |||
| 38 | widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); | 38 | widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); |
| 39 | widget->SetConnectedStatus(players[player].connected); | 39 | widget->SetConnectedStatus(players[player].connected); |
| 40 | widget->SetControllerType(players[player].controller_type); | 40 | widget->SetControllerType(players[player].controller_type); |
| 41 | widget->repaint(); | ||
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | QAction* ControllerDialog::toggleViewAction() { | 44 | QAction* ControllerDialog::toggleViewAction() { |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 23643aea2..485045334 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -12,8 +12,8 @@ | |||
| 12 | #include <QFileInfo> | 12 | #include <QFileInfo> |
| 13 | #include <QSettings> | 13 | #include <QSettings> |
| 14 | 14 | ||
| 15 | #include "common/common_paths.h" | 15 | #include "common/fs/fs.h" |
| 16 | #include "common/file_util.h" | 16 | #include "common/fs/path_util.h" |
| 17 | #include "core/core.h" | 17 | #include "core/core.h" |
| 18 | #include "core/file_sys/card_image.h" | 18 | #include "core/file_sys/card_image.h" |
| 19 | #include "core/file_sys/content_archive.h" | 19 | #include "core/file_sys/content_archive.h" |
| @@ -39,10 +39,11 @@ QString GetGameListCachedObject(const std::string& filename, const std::string& | |||
| 39 | return generator(); | 39 | return generator(); |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 42 | const auto path = |
| 43 | "game_list" + DIR_SEP + filename + '.' + ext; | 43 | Common::FS::PathToUTF8String(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 44 | "game_list" / fmt::format("{}.{}", filename, ext)); | ||
| 44 | 45 | ||
| 45 | Common::FS::CreateFullPath(path); | 46 | void(Common::FS::CreateParentDirs(path)); |
| 46 | 47 | ||
| 47 | if (!Common::FS::Exists(path)) { | 48 | if (!Common::FS::Exists(path)) { |
| 48 | const auto str = generator(); | 49 | const auto str = generator(); |
| @@ -70,12 +71,15 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject( | |||
| 70 | return generator(); | 71 | return generator(); |
| 71 | } | 72 | } |
| 72 | 73 | ||
| 73 | const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 74 | const auto game_list_dir = |
| 74 | "game_list" + DIR_SEP + filename + ".jpeg"; | 75 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list"; |
| 75 | const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + | 76 | const auto jpeg_name = fmt::format("{}.jpeg", filename); |
| 76 | "game_list" + DIR_SEP + filename + ".appname.txt"; | 77 | const auto app_name = fmt::format("{}.appname.txt", filename); |
| 77 | 78 | ||
| 78 | Common::FS::CreateFullPath(path1); | 79 | const auto path1 = Common::FS::PathToUTF8String(game_list_dir / jpeg_name); |
| 80 | const auto path2 = Common::FS::PathToUTF8String(game_list_dir / app_name); | ||
| 81 | |||
| 82 | void(Common::FS::CreateParentDirs(path1)); | ||
| 79 | 83 | ||
| 80 | if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { | 84 | if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { |
| 81 | const auto [icon, nacp] = generator(); | 85 | const auto [icon, nacp] = generator(); |
| @@ -281,23 +285,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 281 | } | 285 | } |
| 282 | } | 286 | } |
| 283 | 287 | ||
| 284 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, | 288 | void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, |
| 285 | unsigned int recursion, GameListDir* parent_dir) { | 289 | GameListDir* parent_dir) { |
| 286 | auto& system = Core::System::GetInstance(); | 290 | auto& system = Core::System::GetInstance(); |
| 287 | 291 | ||
| 288 | const auto callback = [this, target, recursion, parent_dir, | 292 | const auto callback = [this, target, parent_dir, |
| 289 | &system](u64* num_entries_out, const std::string& directory, | 293 | &system](const std::filesystem::path& path) -> bool { |
| 290 | const std::string& virtual_name) -> bool { | ||
| 291 | if (stop_processing) { | 294 | if (stop_processing) { |
| 292 | // Breaks the callback loop. | 295 | // Breaks the callback loop. |
| 293 | return false; | 296 | return false; |
| 294 | } | 297 | } |
| 295 | 298 | ||
| 296 | const std::string physical_name = directory + DIR_SEP + virtual_name; | 299 | const auto physical_name = Common::FS::PathToUTF8String(path); |
| 297 | const bool is_dir = Common::FS::IsDirectory(physical_name); | 300 | const auto is_dir = Common::FS::IsDir(path); |
| 301 | |||
| 298 | if (!is_dir && | 302 | if (!is_dir && |
| 299 | (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { | 303 | (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { |
| 300 | const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); | 304 | const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); |
| 305 | if (!file) { | ||
| 306 | return true; | ||
| 307 | } | ||
| 308 | |||
| 301 | auto loader = Loader::GetLoader(system, file); | 309 | auto loader = Loader::GetLoader(system, file); |
| 302 | if (!loader) { | 310 | if (!loader) { |
| 303 | return true; | 311 | return true; |
| @@ -343,15 +351,19 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 343 | compatibility_list, patch), | 351 | compatibility_list, patch), |
| 344 | parent_dir); | 352 | parent_dir); |
| 345 | } | 353 | } |
| 346 | } else if (is_dir && recursion > 0) { | 354 | } else if (is_dir) { |
| 347 | watch_list.append(QString::fromStdString(physical_name)); | 355 | watch_list.append(QString::fromStdString(physical_name)); |
| 348 | ScanFileSystem(target, physical_name, recursion - 1, parent_dir); | ||
| 349 | } | 356 | } |
| 350 | 357 | ||
| 351 | return true; | 358 | return true; |
| 352 | }; | 359 | }; |
| 353 | 360 | ||
| 354 | Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback); | 361 | if (deep_scan) { |
| 362 | Common::FS::IterateDirEntriesRecursively(dir_path, callback, | ||
| 363 | Common::FS::DirEntryFilter::All); | ||
| 364 | } else { | ||
| 365 | Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); | ||
| 366 | } | ||
| 355 | } | 367 | } |
| 356 | 368 | ||
| 357 | void GameListWorker::run() { | 369 | void GameListWorker::run() { |
| @@ -376,9 +388,9 @@ void GameListWorker::run() { | |||
| 376 | auto* const game_list_dir = new GameListDir(game_dir); | 388 | auto* const game_list_dir = new GameListDir(game_dir); |
| 377 | emit DirEntryReady(game_list_dir); | 389 | emit DirEntryReady(game_list_dir); |
| 378 | ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), | 390 | ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), |
| 379 | game_dir.deep_scan ? 256 : 0, game_list_dir); | 391 | game_dir.deep_scan, game_list_dir); |
| 380 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), | 392 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), |
| 381 | game_dir.deep_scan ? 256 : 0, game_list_dir); | 393 | game_dir.deep_scan, game_list_dir); |
| 382 | } | 394 | } |
| 383 | } | 395 | } |
| 384 | 396 | ||
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 84e4e1b42..396bb2623 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -70,7 +70,7 @@ private: | |||
| 70 | PopulateGameList, | 70 | PopulateGameList, |
| 71 | }; | 71 | }; |
| 72 | 72 | ||
| 73 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, | 73 | void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, |
| 74 | GameListDir* parent_dir); | 74 | GameListDir* parent_dir); |
| 75 | 75 | ||
| 76 | std::shared_ptr<FileSys::VfsFilesystem> vfs; | 76 | std::shared_ptr<FileSys::VfsFilesystem> vfs; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1d36cc02d..37ef62967 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -66,9 +66,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 66 | #include <QtConcurrent/QtConcurrent> | 66 | #include <QtConcurrent/QtConcurrent> |
| 67 | 67 | ||
| 68 | #include <fmt/format.h> | 68 | #include <fmt/format.h> |
| 69 | #include "common/common_paths.h" | ||
| 70 | #include "common/detached_tasks.h" | 69 | #include "common/detached_tasks.h" |
| 71 | #include "common/file_util.h" | 70 | #include "common/fs/fs.h" |
| 71 | #include "common/fs/fs_paths.h" | ||
| 72 | #include "common/fs/path_util.h" | ||
| 72 | #include "common/logging/backend.h" | 73 | #include "common/logging/backend.h" |
| 73 | #include "common/logging/filter.h" | 74 | #include "common/logging/filter.h" |
| 74 | #include "common/logging/log.h" | 75 | #include "common/logging/log.h" |
| @@ -178,36 +179,25 @@ static void InitializeLogging() { | |||
| 178 | log_filter.ParseFilterString(Settings::values.log_filter); | 179 | log_filter.ParseFilterString(Settings::values.log_filter); |
| 179 | Log::SetGlobalFilter(log_filter); | 180 | Log::SetGlobalFilter(log_filter); |
| 180 | 181 | ||
| 181 | const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); | 182 | const auto log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); |
| 182 | FS::CreateFullPath(log_dir); | 183 | void(FS::CreateDir(log_dir)); |
| 183 | Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); | 184 | Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); |
| 184 | #ifdef _WIN32 | 185 | #ifdef _WIN32 |
| 185 | Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); | 186 | Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); |
| 186 | #endif | 187 | #endif |
| 187 | } | 188 | } |
| 188 | 189 | ||
| 189 | static void RemoveCachedContents() { | 190 | static void RemoveCachedContents() { |
| 190 | const auto offline_fonts = Common::FS::SanitizePath( | 191 | const auto cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir); |
| 191 | fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | 192 | const auto offline_fonts = cache_dir / "fonts"; |
| 192 | Common::FS::DirectorySeparator::PlatformDefault); | 193 | const auto offline_manual = cache_dir / "offline_web_applet_manual"; |
| 193 | 194 | const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; | |
| 194 | const auto offline_manual = Common::FS::SanitizePath( | 195 | const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; |
| 195 | fmt::format("{}/offline_web_applet_manual", | 196 | |
| 196 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | 197 | void(Common::FS::RemoveDirRecursively(offline_fonts)); |
| 197 | Common::FS::DirectorySeparator::PlatformDefault); | 198 | void(Common::FS::RemoveDirRecursively(offline_manual)); |
| 198 | const auto offline_legal_information = Common::FS::SanitizePath( | 199 | void(Common::FS::RemoveDirRecursively(offline_legal_information)); |
| 199 | fmt::format("{}/offline_web_applet_legal_information", | 200 | void(Common::FS::RemoveDirRecursively(offline_system_data)); |
| 200 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 201 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 202 | const auto offline_system_data = Common::FS::SanitizePath( | ||
| 203 | fmt::format("{}/offline_web_applet_system_data", | ||
| 204 | Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), | ||
| 205 | Common::FS::DirectorySeparator::PlatformDefault); | ||
| 206 | |||
| 207 | Common::FS::DeleteDirRecursively(offline_fonts); | ||
| 208 | Common::FS::DeleteDirRecursively(offline_manual); | ||
| 209 | Common::FS::DeleteDirRecursively(offline_legal_information); | ||
| 210 | Common::FS::DeleteDirRecursively(offline_system_data); | ||
| 211 | } | 201 | } |
| 212 | 202 | ||
| 213 | GMainWindow::GMainWindow() | 203 | GMainWindow::GMainWindow() |
| @@ -773,10 +763,22 @@ void GMainWindow::InitializeWidgets() { | |||
| 773 | dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); | 763 | dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); |
| 774 | dock_status_button->setFocusPolicy(Qt::NoFocus); | 764 | dock_status_button->setFocusPolicy(Qt::NoFocus); |
| 775 | connect(dock_status_button, &QPushButton::clicked, [&] { | 765 | connect(dock_status_button, &QPushButton::clicked, [&] { |
| 776 | Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue()); | 766 | const bool is_docked = Settings::values.use_docked_mode.GetValue(); |
| 777 | dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); | 767 | auto& controller_type = Settings::values.players.GetValue()[0].controller_type; |
| 778 | OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(), | 768 | |
| 779 | Settings::values.use_docked_mode.GetValue()); | 769 | if (!is_docked && controller_type == Settings::ControllerType::Handheld) { |
| 770 | QMessageBox::warning(this, tr("Invalid config detected"), | ||
| 771 | tr("Handheld controller can't be used on docked mode. Pro " | ||
| 772 | "controller will be selected.")); | ||
| 773 | controller_type = Settings::ControllerType::ProController; | ||
| 774 | ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get()); | ||
| 775 | configure_dialog.ApplyConfiguration(); | ||
| 776 | controller_dialog->refreshConfiguration(); | ||
| 777 | } | ||
| 778 | |||
| 779 | Settings::values.use_docked_mode.SetValue(!is_docked); | ||
| 780 | dock_status_button->setChecked(!is_docked); | ||
| 781 | OnDockedModeChanged(is_docked, !is_docked); | ||
| 780 | }); | 782 | }); |
| 781 | dock_status_button->setText(tr("DOCK")); | 783 | dock_status_button->setText(tr("DOCK")); |
| 782 | dock_status_button->setCheckable(true); | 784 | dock_status_button->setCheckable(true); |
| @@ -1378,7 +1380,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { | |||
| 1378 | game_list->hide(); | 1380 | game_list->hide(); |
| 1379 | game_list_placeholder->hide(); | 1381 | game_list_placeholder->hide(); |
| 1380 | } | 1382 | } |
| 1381 | status_bar_update_timer.start(2000); | 1383 | status_bar_update_timer.start(500); |
| 1382 | async_status_button->setDisabled(true); | 1384 | async_status_button->setDisabled(true); |
| 1383 | multicore_status_button->setDisabled(true); | 1385 | multicore_status_button->setDisabled(true); |
| 1384 | renderer_status_button->setDisabled(true); | 1386 | renderer_status_button->setDisabled(true); |
| @@ -1406,7 +1408,8 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { | |||
| 1406 | title_name = metadata.first->GetApplicationName(); | 1408 | title_name = metadata.first->GetApplicationName(); |
| 1407 | } | 1409 | } |
| 1408 | if (res != Loader::ResultStatus::Success || title_name.empty()) { | 1410 | if (res != Loader::ResultStatus::Success || title_name.empty()) { |
| 1409 | title_name = Common::FS::GetFilename(filename.toStdString()); | 1411 | title_name = Common::FS::PathToUTF8String( |
| 1412 | std::filesystem::path{filename.toStdU16String()}.filename()); | ||
| 1410 | } | 1413 | } |
| 1411 | LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); | 1414 | LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); |
| 1412 | UpdateWindowTitle(title_name, title_version); | 1415 | UpdateWindowTitle(title_name, title_version); |
| @@ -1526,7 +1529,7 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { | |||
| 1526 | 1529 | ||
| 1527 | void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, | 1530 | void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, |
| 1528 | const std::string& game_path) { | 1531 | const std::string& game_path) { |
| 1529 | std::string path; | 1532 | std::filesystem::path path; |
| 1530 | QString open_target; | 1533 | QString open_target; |
| 1531 | auto& system = Core::System::GetInstance(); | 1534 | auto& system = Core::System::GetInstance(); |
| 1532 | 1535 | ||
| @@ -1555,7 +1558,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | |||
| 1555 | switch (target) { | 1558 | switch (target) { |
| 1556 | case GameListOpenTarget::SaveData: { | 1559 | case GameListOpenTarget::SaveData: { |
| 1557 | open_target = tr("Save Data"); | 1560 | open_target = tr("Save Data"); |
| 1558 | const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir); | 1561 | const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); |
| 1559 | 1562 | ||
| 1560 | if (has_user_save) { | 1563 | if (has_user_save) { |
| 1561 | // User save data | 1564 | // User save data |
| @@ -1580,34 +1583,38 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | |||
| 1580 | Service::Account::ProfileManager manager; | 1583 | Service::Account::ProfileManager manager; |
| 1581 | const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); | 1584 | const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); |
| 1582 | ASSERT(user_id); | 1585 | ASSERT(user_id); |
| 1583 | path = nand_dir + FileSys::SaveDataFactory::GetFullPath( | 1586 | |
| 1584 | system, FileSys::SaveDataSpaceId::NandUser, | 1587 | const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( |
| 1585 | FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0); | 1588 | system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, |
| 1589 | program_id, user_id->uuid, 0); | ||
| 1590 | |||
| 1591 | path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path); | ||
| 1586 | } else { | 1592 | } else { |
| 1587 | // Device save data | 1593 | // Device save data |
| 1588 | path = nand_dir + FileSys::SaveDataFactory::GetFullPath( | 1594 | const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath( |
| 1589 | system, FileSys::SaveDataSpaceId::NandUser, | 1595 | system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, |
| 1590 | FileSys::SaveDataType::SaveData, program_id, {}, 0); | 1596 | program_id, {}, 0); |
| 1597 | |||
| 1598 | path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path); | ||
| 1591 | } | 1599 | } |
| 1592 | 1600 | ||
| 1593 | if (!Common::FS::Exists(path)) { | 1601 | if (!Common::FS::CreateDirs(path)) { |
| 1594 | Common::FS::CreateFullPath(path); | 1602 | LOG_ERROR(Frontend, "Unable to create the directories for save data"); |
| 1595 | Common::FS::CreateDir(path); | ||
| 1596 | } | 1603 | } |
| 1597 | 1604 | ||
| 1598 | break; | 1605 | break; |
| 1599 | } | 1606 | } |
| 1600 | case GameListOpenTarget::ModData: { | 1607 | case GameListOpenTarget::ModData: { |
| 1601 | open_target = tr("Mod Data"); | 1608 | open_target = tr("Mod Data"); |
| 1602 | const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir); | 1609 | path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir) / |
| 1603 | path = fmt::format("{}{:016X}", load_dir, program_id); | 1610 | fmt::format("{:016X}", program_id); |
| 1604 | break; | 1611 | break; |
| 1605 | } | 1612 | } |
| 1606 | default: | 1613 | default: |
| 1607 | UNIMPLEMENTED(); | 1614 | UNIMPLEMENTED(); |
| 1608 | } | 1615 | } |
| 1609 | 1616 | ||
| 1610 | const QString qpath = QString::fromStdString(path); | 1617 | const QString qpath = QString::fromStdString(Common::FS::PathToUTF8String(path)); |
| 1611 | const QDir dir(qpath); | 1618 | const QDir dir(qpath); |
| 1612 | if (!dir.exists()) { | 1619 | if (!dir.exists()) { |
| 1613 | QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target), | 1620 | QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target), |
| @@ -1620,33 +1627,35 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | |||
| 1620 | } | 1627 | } |
| 1621 | 1628 | ||
| 1622 | void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { | 1629 | void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { |
| 1623 | const QString shader_dir = | 1630 | const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir); |
| 1624 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); | 1631 | const auto transferable_shader_cache_folder_path = shader_cache_dir / "opengl" / "transferable"; |
| 1625 | const QString transferable_shader_cache_folder_path = | 1632 | const auto transferable_shader_cache_file_path = |
| 1626 | shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); | 1633 | transferable_shader_cache_folder_path / fmt::format("{:016X}.bin", program_id); |
| 1627 | const QString transferable_shader_cache_file_path = | 1634 | |
| 1628 | transferable_shader_cache_folder_path + QDir::separator() + | 1635 | if (!Common::FS::Exists(transferable_shader_cache_file_path)) { |
| 1629 | QString::fromStdString(fmt::format("{:016X}.bin", program_id)); | ||
| 1630 | |||
| 1631 | if (!QFile::exists(transferable_shader_cache_file_path)) { | ||
| 1632 | QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), | 1636 | QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), |
| 1633 | tr("A shader cache for this title does not exist.")); | 1637 | tr("A shader cache for this title does not exist.")); |
| 1634 | return; | 1638 | return; |
| 1635 | } | 1639 | } |
| 1636 | 1640 | ||
| 1641 | const auto qt_shader_cache_folder_path = | ||
| 1642 | QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_folder_path)); | ||
| 1643 | const auto qt_shader_cache_file_path = | ||
| 1644 | QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_file_path)); | ||
| 1645 | |||
| 1637 | // Windows supports opening a folder with selecting a specified file in explorer. On every other | 1646 | // Windows supports opening a folder with selecting a specified file in explorer. On every other |
| 1638 | // OS we just open the transferable shader cache folder without preselecting the transferable | 1647 | // OS we just open the transferable shader cache folder without preselecting the transferable |
| 1639 | // shader cache file for the selected game. | 1648 | // shader cache file for the selected game. |
| 1640 | #if defined(Q_OS_WIN) | 1649 | #if defined(Q_OS_WIN) |
| 1641 | const QString explorer = QStringLiteral("explorer"); | 1650 | const QString explorer = QStringLiteral("explorer"); |
| 1642 | QStringList param; | 1651 | QStringList param; |
| 1643 | if (!QFileInfo(transferable_shader_cache_file_path).isDir()) { | 1652 | if (!QFileInfo(qt_shader_cache_file_path).isDir()) { |
| 1644 | param << QStringLiteral("/select,"); | 1653 | param << QStringLiteral("/select,"); |
| 1645 | } | 1654 | } |
| 1646 | param << QDir::toNativeSeparators(transferable_shader_cache_file_path); | 1655 | param << QDir::toNativeSeparators(qt_shader_cache_file_path); |
| 1647 | QProcess::startDetached(explorer, param); | 1656 | QProcess::startDetached(explorer, param); |
| 1648 | #else | 1657 | #else |
| 1649 | QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); | 1658 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_folder_path)); |
| 1650 | #endif | 1659 | #endif |
| 1651 | } | 1660 | } |
| 1652 | 1661 | ||
| @@ -1724,8 +1733,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT | |||
| 1724 | RemoveAddOnContent(program_id, entry_type); | 1733 | RemoveAddOnContent(program_id, entry_type); |
| 1725 | break; | 1734 | break; |
| 1726 | } | 1735 | } |
| 1727 | Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + | 1736 | void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 1728 | DIR_SEP + "game_list"); | 1737 | "game_list")); |
| 1729 | game_list->PopulateAsync(UISettings::values.game_dirs); | 1738 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 1730 | } | 1739 | } |
| 1731 | 1740 | ||
| @@ -1814,21 +1823,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ | |||
| 1814 | } | 1823 | } |
| 1815 | 1824 | ||
| 1816 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { | 1825 | void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { |
| 1817 | const QString shader_dir = | 1826 | const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir); |
| 1818 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); | 1827 | const auto transferable_shader_cache_file_path = |
| 1819 | const QString transferable_shader_cache_folder_path = | 1828 | shader_cache_dir / "opengl" / "transferable" / fmt::format("{:016X}.bin", program_id); |
| 1820 | shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); | 1829 | |
| 1821 | const QString transferable_shader_cache_file_path = | 1830 | if (!Common::FS::Exists(transferable_shader_cache_file_path)) { |
| 1822 | transferable_shader_cache_folder_path + QDir::separator() + | ||
| 1823 | QString::fromStdString(fmt::format("{:016X}.bin", program_id)); | ||
| 1824 | |||
| 1825 | if (!QFile::exists(transferable_shader_cache_file_path)) { | ||
| 1826 | QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), | 1831 | QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), |
| 1827 | tr("A shader cache for this title does not exist.")); | 1832 | tr("A shader cache for this title does not exist.")); |
| 1828 | return; | 1833 | return; |
| 1829 | } | 1834 | } |
| 1830 | 1835 | ||
| 1831 | if (QFile::remove(transferable_shader_cache_file_path)) { | 1836 | if (Common::FS::RemoveFile(transferable_shader_cache_file_path)) { |
| 1832 | QMessageBox::information(this, tr("Successfully Removed"), | 1837 | QMessageBox::information(this, tr("Successfully Removed"), |
| 1833 | tr("Successfully removed the transferable shader cache.")); | 1838 | tr("Successfully removed the transferable shader cache.")); |
| 1834 | } else { | 1839 | } else { |
| @@ -1838,19 +1843,16 @@ void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { | |||
| 1838 | } | 1843 | } |
| 1839 | 1844 | ||
| 1840 | void GMainWindow::RemoveCustomConfiguration(u64 program_id) { | 1845 | void GMainWindow::RemoveCustomConfiguration(u64 program_id) { |
| 1841 | const QString config_dir = | 1846 | const auto custom_config_file_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / |
| 1842 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); | 1847 | "custom" / fmt::format("{:016X}.ini", program_id); |
| 1843 | const QString custom_config_file_path = | ||
| 1844 | config_dir + QStringLiteral("custom") + QDir::separator() + | ||
| 1845 | QString::fromStdString(fmt::format("{:016X}.ini", program_id)); | ||
| 1846 | 1848 | ||
| 1847 | if (!QFile::exists(custom_config_file_path)) { | 1849 | if (!Common::FS::Exists(custom_config_file_path)) { |
| 1848 | QMessageBox::warning(this, tr("Error Removing Custom Configuration"), | 1850 | QMessageBox::warning(this, tr("Error Removing Custom Configuration"), |
| 1849 | tr("A custom configuration for this title does not exist.")); | 1851 | tr("A custom configuration for this title does not exist.")); |
| 1850 | return; | 1852 | return; |
| 1851 | } | 1853 | } |
| 1852 | 1854 | ||
| 1853 | if (QFile::remove(custom_config_file_path)) { | 1855 | if (Common::FS::RemoveFile(custom_config_file_path)) { |
| 1854 | QMessageBox::information(this, tr("Successfully Removed"), | 1856 | QMessageBox::information(this, tr("Successfully Removed"), |
| 1855 | tr("Successfully removed the custom game configuration.")); | 1857 | tr("Successfully removed the custom game configuration.")); |
| 1856 | } else { | 1858 | } else { |
| @@ -1887,8 +1889,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 1887 | return; | 1889 | return; |
| 1888 | } | 1890 | } |
| 1889 | 1891 | ||
| 1890 | const auto path = fmt::format( | 1892 | const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir); |
| 1891 | "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id); | 1893 | const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); |
| 1894 | |||
| 1895 | const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); | ||
| 1892 | 1896 | ||
| 1893 | FileSys::VirtualFile romfs; | 1897 | FileSys::VirtualFile romfs; |
| 1894 | 1898 | ||
| @@ -1966,24 +1970,29 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | |||
| 1966 | } | 1970 | } |
| 1967 | 1971 | ||
| 1968 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { | 1972 | void GMainWindow::OnGameListOpenDirectory(const QString& directory) { |
| 1969 | QString path; | 1973 | std::filesystem::path fs_path; |
| 1970 | if (directory == QStringLiteral("SDMC")) { | 1974 | if (directory == QStringLiteral("SDMC")) { |
| 1971 | path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + | 1975 | fs_path = |
| 1972 | "Nintendo/Contents/registered"); | 1976 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/registered"; |
| 1973 | } else if (directory == QStringLiteral("UserNAND")) { | 1977 | } else if (directory == QStringLiteral("UserNAND")) { |
| 1974 | path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 1978 | fs_path = |
| 1975 | "user/Contents/registered"); | 1979 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "user/Contents/registered"; |
| 1976 | } else if (directory == QStringLiteral("SysNAND")) { | 1980 | } else if (directory == QStringLiteral("SysNAND")) { |
| 1977 | path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + | 1981 | fs_path = |
| 1978 | "system/Contents/registered"); | 1982 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/Contents/registered"; |
| 1979 | } else { | 1983 | } else { |
| 1980 | path = directory; | 1984 | fs_path = directory.toStdString(); |
| 1981 | } | 1985 | } |
| 1982 | if (!QFileInfo::exists(path)) { | 1986 | |
| 1983 | QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); | 1987 | const auto qt_path = QString::fromStdString(Common::FS::PathToUTF8String(fs_path)); |
| 1988 | |||
| 1989 | if (!Common::FS::IsDir(fs_path)) { | ||
| 1990 | QMessageBox::critical(this, tr("Error Opening %1").arg(qt_path), | ||
| 1991 | tr("Folder does not exist!")); | ||
| 1984 | return; | 1992 | return; |
| 1985 | } | 1993 | } |
| 1986 | QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | 1994 | |
| 1995 | QDesktopServices::openUrl(QUrl::fromLocalFile(qt_path)); | ||
| 1987 | } | 1996 | } |
| 1988 | 1997 | ||
| 1989 | void GMainWindow::OnGameListAddDirectory() { | 1998 | void GMainWindow::OnGameListAddDirectory() { |
| @@ -2177,8 +2186,8 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 2177 | : tr("%n file(s) failed to install\n", "", failed_files.size())); | 2186 | : tr("%n file(s) failed to install\n", "", failed_files.size())); |
| 2178 | 2187 | ||
| 2179 | QMessageBox::information(this, tr("Install Results"), install_results); | 2188 | QMessageBox::information(this, tr("Install Results"), install_results); |
| 2180 | Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + | 2189 | void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / |
| 2181 | DIR_SEP + "game_list"); | 2190 | "game_list")); |
| 2182 | game_list->PopulateAsync(UISettings::values.game_dirs); | 2191 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 2183 | ui.action_Install_File_NAND->setEnabled(true); | 2192 | ui.action_Install_File_NAND->setEnabled(true); |
| 2184 | } | 2193 | } |
| @@ -2694,7 +2703,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { | |||
| 2694 | 2703 | ||
| 2695 | void GMainWindow::OnOpenYuzuFolder() { | 2704 | void GMainWindow::OnOpenYuzuFolder() { |
| 2696 | QDesktopServices::openUrl(QUrl::fromLocalFile( | 2705 | QDesktopServices::openUrl(QUrl::fromLocalFile( |
| 2697 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir)))); | 2706 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); |
| 2698 | } | 2707 | } |
| 2699 | 2708 | ||
| 2700 | void GMainWindow::OnAbout() { | 2709 | void GMainWindow::OnAbout() { |
| @@ -2716,7 +2725,7 @@ void GMainWindow::OnCaptureScreenshot() { | |||
| 2716 | 2725 | ||
| 2717 | const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); | 2726 | const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); |
| 2718 | const auto screenshot_path = | 2727 | const auto screenshot_path = |
| 2719 | QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)); | 2728 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)); |
| 2720 | const auto date = | 2729 | const auto date = |
| 2721 | QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); | 2730 | QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); |
| 2722 | QString filename = QStringLiteral("%1%2_%3.png") | 2731 | QString filename = QStringLiteral("%1%2_%3.png") |
| @@ -2745,23 +2754,26 @@ void GMainWindow::OnCaptureScreenshot() { | |||
| 2745 | 2754 | ||
| 2746 | // TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant | 2755 | // TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant |
| 2747 | void GMainWindow::MigrateConfigFiles() { | 2756 | void GMainWindow::MigrateConfigFiles() { |
| 2748 | const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir); | 2757 | const auto config_dir_fs_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir); |
| 2749 | const QDir config_dir = QDir(QString::fromStdString(config_dir_str)); | 2758 | const QDir config_dir = |
| 2759 | QDir(QString::fromStdString(Common::FS::PathToUTF8String(config_dir_fs_path))); | ||
| 2750 | const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini"))); | 2760 | const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini"))); |
| 2751 | 2761 | ||
| 2752 | Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str)); | 2762 | if (!Common::FS::CreateDirs(config_dir_fs_path / "custom")) { |
| 2753 | for (QStringList::const_iterator it = config_dir_list.constBegin(); | 2763 | LOG_ERROR(Frontend, "Failed to create new config file directory"); |
| 2754 | it != config_dir_list.constEnd(); ++it) { | 2764 | } |
| 2765 | |||
| 2766 | for (auto it = config_dir_list.constBegin(); it != config_dir_list.constEnd(); ++it) { | ||
| 2755 | const auto filename = it->toStdString(); | 2767 | const auto filename = it->toStdString(); |
| 2756 | if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) { | 2768 | if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) { |
| 2757 | continue; | 2769 | continue; |
| 2758 | } | 2770 | } |
| 2759 | const auto origin = fmt::format("{}{}", config_dir_str, filename); | 2771 | const auto origin = config_dir_fs_path / filename; |
| 2760 | const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename); | 2772 | const auto destination = config_dir_fs_path / "custom" / filename; |
| 2761 | LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); | 2773 | LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); |
| 2762 | if (!Common::FS::Rename(origin, destination)) { | 2774 | if (!Common::FS::RenameFile(origin, destination)) { |
| 2763 | // Delete the old config file if one already exists in the new location. | 2775 | // Delete the old config file if one already exists in the new location. |
| 2764 | Common::FS::Delete(origin); | 2776 | void(Common::FS::RemoveFile(origin)); |
| 2765 | } | 2777 | } |
| 2766 | } | 2778 | } |
| 2767 | } | 2779 | } |
| @@ -2809,7 +2821,7 @@ void GMainWindow::UpdateStatusBar() { | |||
| 2809 | } else { | 2821 | } else { |
| 2810 | emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); | 2822 | emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); |
| 2811 | } | 2823 | } |
| 2812 | game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); | 2824 | game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0)); |
| 2813 | emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); | 2825 | emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); |
| 2814 | 2826 | ||
| 2815 | emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); | 2827 | emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); |
| @@ -2953,18 +2965,16 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { | |||
| 2953 | if (res == QMessageBox::Cancel) | 2965 | if (res == QMessageBox::Cancel) |
| 2954 | return; | 2966 | return; |
| 2955 | 2967 | ||
| 2956 | Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + | 2968 | const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); |
| 2957 | "prod.keys_autogenerated"); | 2969 | |
| 2958 | Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + | 2970 | void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated")); |
| 2959 | "console.keys_autogenerated"); | 2971 | void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated")); |
| 2960 | Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + | 2972 | void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated")); |
| 2961 | "title.keys_autogenerated"); | ||
| 2962 | } | 2973 | } |
| 2963 | 2974 | ||
| 2964 | Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); | 2975 | Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); |
| 2965 | if (keys.BaseDeriveNecessary()) { | 2976 | if (keys.BaseDeriveNecessary()) { |
| 2966 | Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( | 2977 | Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; |
| 2967 | Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)}; | ||
| 2968 | 2978 | ||
| 2969 | const auto function = [this, &keys, &pdm] { | 2979 | const auto function = [this, &keys, &pdm] { |
| 2970 | keys.PopulateFromPartitionData(pdm); | 2980 | keys.PopulateFromPartitionData(pdm); |
| @@ -3277,12 +3287,17 @@ int main(int argc, char* argv[]) { | |||
| 3277 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); | 3287 | QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); |
| 3278 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); | 3288 | QCoreApplication::setApplicationName(QStringLiteral("yuzu")); |
| 3279 | 3289 | ||
| 3290 | #ifdef _WIN32 | ||
| 3291 | // Increases the maximum open file limit to 4096 | ||
| 3292 | _setmaxstdio(4096); | ||
| 3293 | #endif | ||
| 3294 | |||
| 3280 | #ifdef __APPLE__ | 3295 | #ifdef __APPLE__ |
| 3281 | // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". | 3296 | // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". |
| 3282 | // But since we require the working directory to be the executable path for the location of | 3297 | // But since we require the working directory to be the executable path for the location of |
| 3283 | // the user folder in the Qt Frontend, we need to cd into that working directory | 3298 | // the user folder in the Qt Frontend, we need to cd into that working directory |
| 3284 | const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + ".."; | 3299 | const auto bin_path = Common::FS::GetBundleDirectory() / ".."; |
| 3285 | chdir(bin_path.c_str()); | 3300 | chdir(Common::FS::PathToUTF8String(bin_path).c_str()); |
| 3286 | #endif | 3301 | #endif |
| 3287 | 3302 | ||
| 3288 | #ifdef __linux__ | 3303 | #ifdef __linux__ |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 7e1d5f379..a2ab69cdd 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -16,7 +16,9 @@ | |||
| 16 | #endif | 16 | #endif |
| 17 | 17 | ||
| 18 | #include <inih/cpp/INIReader.h> | 18 | #include <inih/cpp/INIReader.h> |
| 19 | #include "common/file_util.h" | 19 | #include "common/fs/file.h" |
| 20 | #include "common/fs/fs.h" | ||
| 21 | #include "common/fs/path_util.h" | ||
| 20 | #include "common/logging/log.h" | 22 | #include "common/logging/log.h" |
| 21 | #include "common/param_package.h" | 23 | #include "common/param_package.h" |
| 22 | #include "common/settings.h" | 24 | #include "common/settings.h" |
| @@ -30,8 +32,8 @@ namespace FS = Common::FS; | |||
| 30 | 32 | ||
| 31 | Config::Config() { | 33 | Config::Config() { |
| 32 | // TODO: Don't hardcode the path; let the frontend decide where to put the config files. | 34 | // TODO: Don't hardcode the path; let the frontend decide where to put the config files. |
| 33 | sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini"; | 35 | sdl2_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini"; |
| 34 | sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); | 36 | sdl2_config = std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc)); |
| 35 | 37 | ||
| 36 | Reload(); | 38 | Reload(); |
| 37 | } | 39 | } |
| @@ -39,20 +41,23 @@ Config::Config() { | |||
| 39 | Config::~Config() = default; | 41 | Config::~Config() = default; |
| 40 | 42 | ||
| 41 | bool Config::LoadINI(const std::string& default_contents, bool retry) { | 43 | bool Config::LoadINI(const std::string& default_contents, bool retry) { |
| 42 | const std::string& location = this->sdl2_config_loc; | 44 | const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc); |
| 43 | if (sdl2_config->ParseError() < 0) { | 45 | if (sdl2_config->ParseError() < 0) { |
| 44 | if (retry) { | 46 | if (retry) { |
| 45 | LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); | 47 | LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", |
| 46 | FS::CreateFullPath(location); | 48 | config_loc_str); |
| 47 | FS::WriteStringToFile(true, location, default_contents); | 49 | |
| 48 | sdl2_config = std::make_unique<INIReader>(location); // Reopen file | 50 | void(FS::CreateParentDir(sdl2_config_loc)); |
| 51 | void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents)); | ||
| 52 | |||
| 53 | sdl2_config = std::make_unique<INIReader>(config_loc_str); | ||
| 49 | 54 | ||
| 50 | return LoadINI(default_contents, false); | 55 | return LoadINI(default_contents, false); |
| 51 | } | 56 | } |
| 52 | LOG_ERROR(Config, "Failed."); | 57 | LOG_ERROR(Config, "Failed."); |
| 53 | return false; | 58 | return false; |
| 54 | } | 59 | } |
| 55 | LOG_INFO(Config, "Successfully loaded {}", location); | 60 | LOG_INFO(Config, "Successfully loaded {}", config_loc_str); |
| 56 | return true; | 61 | return true; |
| 57 | } | 62 | } |
| 58 | 63 | ||
| @@ -327,18 +332,18 @@ void Config::ReadValues() { | |||
| 327 | // Data Storage | 332 | // Data Storage |
| 328 | Settings::values.use_virtual_sd = | 333 | Settings::values.use_virtual_sd = |
| 329 | sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); | 334 | sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); |
| 330 | FS::GetUserPath( | 335 | FS::SetYuzuPath(FS::YuzuPath::NANDDir, |
| 331 | FS::UserPath::NANDDir, | 336 | sdl2_config->Get("Data Storage", "nand_directory", |
| 332 | sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir))); | 337 | FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); |
| 333 | FS::GetUserPath( | 338 | FS::SetYuzuPath(FS::YuzuPath::SDMCDir, |
| 334 | FS::UserPath::SDMCDir, | 339 | sdl2_config->Get("Data Storage", "sdmc_directory", |
| 335 | sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir))); | 340 | FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); |
| 336 | FS::GetUserPath( | 341 | FS::SetYuzuPath(FS::YuzuPath::LoadDir, |
| 337 | FS::UserPath::LoadDir, | 342 | sdl2_config->Get("Data Storage", "load_directory", |
| 338 | sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir))); | 343 | FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); |
| 339 | FS::GetUserPath( | 344 | FS::SetYuzuPath(FS::YuzuPath::DumpDir, |
| 340 | FS::UserPath::DumpDir, | 345 | sdl2_config->Get("Data Storage", "dump_directory", |
| 341 | sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); | 346 | FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); |
| 342 | Settings::values.gamecard_inserted = | 347 | Settings::values.gamecard_inserted = |
| 343 | sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); | 348 | sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); |
| 344 | Settings::values.gamecard_current_game = | 349 | Settings::values.gamecard_current_game = |
| @@ -361,10 +366,10 @@ void Config::ReadValues() { | |||
| 361 | 366 | ||
| 362 | const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); | 367 | const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); |
| 363 | if (custom_rtc_enabled) { | 368 | if (custom_rtc_enabled) { |
| 364 | Settings::values.custom_rtc.SetValue( | 369 | Settings::values.custom_rtc = |
| 365 | std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0))); | 370 | std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); |
| 366 | } else { | 371 | } else { |
| 367 | Settings::values.custom_rtc.SetValue(std::nullopt); | 372 | Settings::values.custom_rtc = std::nullopt; |
| 368 | } | 373 | } |
| 369 | 374 | ||
| 370 | Settings::values.language_index.SetValue( | 375 | Settings::values.language_index.SetValue( |
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h index abc90f642..807199278 100644 --- a/src/yuzu_cmd/config.h +++ b/src/yuzu_cmd/config.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <filesystem> | ||
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <string> | 9 | #include <string> |
| 9 | 10 | ||
| @@ -11,7 +12,7 @@ class INIReader; | |||
| 11 | 12 | ||
| 12 | class Config { | 13 | class Config { |
| 13 | std::unique_ptr<INIReader> sdl2_config; | 14 | std::unique_ptr<INIReader> sdl2_config; |
| 14 | std::string sdl2_config_loc; | 15 | std::filesystem::path sdl2_config_loc; |
| 15 | 16 | ||
| 16 | bool LoadINI(const std::string& default_contents = "", bool retry = true); | 17 | bool LoadINI(const std::string& default_contents = "", bool retry = true); |
| 17 | void ReadValues(); | 18 | void ReadValues(); |
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index d64f81106..06b20c975 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp | |||
| @@ -215,7 +215,7 @@ void EmuWindow_SDL2::WaitEvent() { | |||
| 215 | const auto results = Core::System::GetInstance().GetAndResetPerfStats(); | 215 | const auto results = Core::System::GetInstance().GetAndResetPerfStats(); |
| 216 | const auto title = | 216 | const auto title = |
| 217 | fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, | 217 | fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, |
| 218 | Common::g_scm_branch, Common::g_scm_desc, results.game_fps, | 218 | Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, |
| 219 | results.emulation_speed * 100.0); | 219 | results.emulation_speed * 100.0); |
| 220 | SDL_SetWindowTitle(render_window, title.c_str()); | 220 | SDL_SetWindowTitle(render_window, title.c_str()); |
| 221 | last_time = current_time; | 221 | last_time = current_time; |
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index e2812ca61..584967f5c 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp | |||
| @@ -10,9 +10,10 @@ | |||
| 10 | 10 | ||
| 11 | #include <fmt/ostream.h> | 11 | #include <fmt/ostream.h> |
| 12 | 12 | ||
| 13 | #include "common/common_paths.h" | ||
| 14 | #include "common/detached_tasks.h" | 13 | #include "common/detached_tasks.h" |
| 15 | #include "common/file_util.h" | 14 | #include "common/fs/fs.h" |
| 15 | #include "common/fs/fs_paths.h" | ||
| 16 | #include "common/fs/path_util.h" | ||
| 16 | #include "common/logging/backend.h" | 17 | #include "common/logging/backend.h" |
| 17 | #include "common/logging/filter.h" | 18 | #include "common/logging/filter.h" |
| 18 | #include "common/logging/log.h" | 19 | #include "common/logging/log.h" |
| @@ -82,9 +83,9 @@ static void InitializeLogging() { | |||
| 82 | 83 | ||
| 83 | Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | 84 | Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); |
| 84 | 85 | ||
| 85 | const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); | 86 | const auto& log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); |
| 86 | FS::CreateFullPath(log_dir); | 87 | void(FS::CreateDir(log_dir)); |
| 87 | Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); | 88 | Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); |
| 88 | #ifdef _WIN32 | 89 | #ifdef _WIN32 |
| 89 | Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); | 90 | Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); |
| 90 | #endif | 91 | #endif |