diff options
132 files changed, 4483 insertions, 2919 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3faa2b5ac..e70f29636 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,7 +174,7 @@ macro(yuzu_find_packages) "lz4 1.8 lz4/1.9.2" "nlohmann_json 3.8 nlohmann_json/3.8.0" "ZLIB 1.2 zlib/1.2.11" - "zstd 1.4 zstd/1.4.8" + "zstd 1.5 zstd/1.5.0" # can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068 #"opus 1.3 opus/1.3.1" ) 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 cityhash.cpp cityhash.h common_funcs.h - common_paths.h common_sizes.h common_types.h concepts.h @@ -118,8 +117,16 @@ add_library(common STATIC dynamic_library.h fiber.cpp fiber.h - file_util.cpp - file_util.h + fs/file.cpp + fs/file.h + fs/fs.cpp + fs/fs.h + fs/fs_paths.h + fs/fs_types.h + fs/fs_util.cpp + fs/fs_util.h + fs/path_util.cpp + fs/path_util.h hash.h hex_util.cpp 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 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -// Directory separators, do we need this? -#define DIR_SEP "/" -#define DIR_SEP_CHR '/' - -#ifndef MAX_PATH -#define MAX_PATH 260 -#endif - -// The user data dir -#define ROOT_DIR "." -#define USERDATA_DIR "user" -#ifdef USER_DIR -#define EMU_DATA_DIR USER_DIR -#else -#define EMU_DATA_DIR "yuzu" -#endif - -// Dirs in both User and Sys -#define EUR_DIR "EUR" -#define USA_DIR "USA" -#define JAP_DIR "JAP" - -// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir) -#define CONFIG_DIR "config" -#define CACHE_DIR "cache" -#define SDMC_DIR "sdmc" -#define NAND_DIR "nand" -#define SYSDATA_DIR "sysdata" -#define KEYS_DIR "keys" -#define LOAD_DIR "load" -#define DUMP_DIR "dump" -#define SCREENSHOTS_DIR "screenshots" -#define SHADER_DIR "shader" -#define LOG_DIR "log" - -// Filenames -// Files in the directory returned by GetUserPath(UserPath::ConfigDir) -#define EMU_CONFIG "emu.ini" -#define DEBUGGER_CONFIG "debugger.ini" -#define LOGGER_CONFIG "logger.ini" -// Files in the directory returned by GetUserPath(UserPath::LogDir) -#define LOG_FILE "yuzu_log.txt" - -// Sys files -#define SHARED_FONT "shared_font.bin" -#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 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <limits> -#include <memory> -#include <sstream> -#include <unordered_map> -#include "common/assert.h" -#include "common/common_funcs.h" -#include "common/common_paths.h" -#include "common/file_util.h" -#include "common/logging/log.h" - -#ifdef _WIN32 -#include <windows.h> -// windows.h needs to be included before other windows headers -#include <direct.h> // getcwd -#include <io.h> -#include <shellapi.h> -#include <shlobj.h> // for SHGetFolderPath -#include <tchar.h> -#include "common/string_util.h" - -#ifdef _MSC_VER -// 64 bit offsets for MSVC -#define fseeko _fseeki64 -#define ftello _ftelli64 -#define fileno _fileno -#endif - -// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64 -#define stat _stat64 -#define fstat _fstat64 - -#else -#ifdef __APPLE__ -#include <sys/param.h> -#endif -#include <cctype> -#include <cerrno> -#include <cstdlib> -#include <cstring> -#include <dirent.h> -#include <pwd.h> -#include <unistd.h> -#endif - -#if defined(__APPLE__) -// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just -// ignore them if we're not using clang. The macro is only used to prevent linking against -// functions that don't exist on older versions of macOS, and the worst case scenario is a linker -// error, so this is perfectly safe, just inconvenient. -#ifndef __clang__ -#define availability(...) -#endif -#include <CoreFoundation/CFBundle.h> -#include <CoreFoundation/CFString.h> -#include <CoreFoundation/CFURL.h> -#ifdef availability -#undef availability -#endif - -#endif - -#include <algorithm> -#include <sys/stat.h> - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) -#endif - -// This namespace has various generic functions related to files and paths. -// The code still needs a ton of cleanup. -// REMEMBER: strdup considered harmful! -namespace Common::FS { - -// Remove any ending forward slashes from directory paths -// Modifies argument. -static void StripTailDirSlashes(std::string& fname) { - if (fname.length() <= 1) { - return; - } - - std::size_t i = fname.length(); - while (i > 0 && fname[i - 1] == DIR_SEP_CHR) { - --i; - } - fname.resize(i); -} - -bool Exists(const std::string& filename) { - struct stat file_info; - - std::string copy(filename); - StripTailDirSlashes(copy); - -#ifdef _WIN32 - // Windows needs a slash to identify a driver root - if (copy.size() != 0 && copy.back() == ':') - copy += DIR_SEP_CHR; - - int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); -#else - int result = stat(copy.c_str(), &file_info); -#endif - - return (result == 0); -} - -bool IsDirectory(const std::string& filename) { - struct stat file_info; - - std::string copy(filename); - StripTailDirSlashes(copy); - -#ifdef _WIN32 - // Windows needs a slash to identify a driver root - if (copy.size() != 0 && copy.back() == ':') - copy += DIR_SEP_CHR; - - int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); -#else - int result = stat(copy.c_str(), &file_info); -#endif - - if (result < 0) { - LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg()); - return false; - } - - return S_ISDIR(file_info.st_mode); -} - -bool Delete(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "file {}", filename); - - // Return true because we care about the file no - // being there, not the actual delete. - if (!Exists(filename)) { - LOG_DEBUG(Common_Filesystem, "{} does not exist", filename); - return true; - } - - // We can't delete a directory - if (IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename); - return false; - } - -#ifdef _WIN32 - if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { - LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg()); - return false; - } -#else - if (unlink(filename.c_str()) == -1) { - LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); - return false; - } -#endif - - return true; -} - -bool CreateDir(const std::string& path) { - LOG_TRACE(Common_Filesystem, "directory {}", path); -#ifdef _WIN32 - if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr)) - return true; - DWORD error = GetLastError(); - if (error == ERROR_ALREADY_EXISTS) { - LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path); - return true; - } - LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error); - return false; -#else - if (mkdir(path.c_str(), 0755) == 0) - return true; - - int err = errno; - - if (err == EEXIST) { - LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); - return true; - } - - LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); - return false; -#endif -} - -bool CreateFullPath(const std::string& fullPath) { - int panicCounter = 100; - LOG_TRACE(Common_Filesystem, "path {}", fullPath); - - if (Exists(fullPath)) { - LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath); - return true; - } - - std::size_t position = 0; - while (true) { - // Find next sub path - position = fullPath.find(DIR_SEP_CHR, position); - - // we're done, yay! - if (position == fullPath.npos) - return true; - - // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") - std::string const subPath(fullPath.substr(0, position + 1)); - if (!IsDirectory(subPath) && !CreateDir(subPath)) { - LOG_ERROR(Common, "CreateFullPath: directory creation failed"); - return false; - } - - // A safety check - panicCounter--; - if (panicCounter <= 0) { - LOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); - return false; - } - position++; - } -} - -bool DeleteDir(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "directory {}", filename); - - // check if a directory - if (!IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); - return false; - } - -#ifdef _WIN32 - if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str())) - return true; -#else - if (rmdir(filename.c_str()) == 0) - return true; -#endif - LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); - - return false; -} - -bool Rename(const std::string& srcFilename, const std::string& destFilename) { - LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); -#ifdef _WIN32 - if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(), - Common::UTF8ToUTF16W(destFilename).c_str()) == 0) - return true; -#else - if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) - return true; -#endif - LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, - GetLastErrorMsg()); - return false; -} - -bool Copy(const std::string& srcFilename, const std::string& destFilename) { - LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); -#ifdef _WIN32 - if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(), - Common::UTF8ToUTF16W(destFilename).c_str(), FALSE)) - return true; - - LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, - GetLastErrorMsg()); - return false; -#else - using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>; - - // Open input file - CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose}; - if (!input) { - LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - - // open output file - CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose}; - if (!output) { - LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - - // copy loop - std::array<char, 1024> buffer; - while (!feof(input.get())) { - // read input - std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get()); - if (rnum != buffer.size()) { - if (ferror(input.get()) != 0) { - LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", - srcFilename, destFilename, GetLastErrorMsg()); - return false; - } - } - - // write output - std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get()); - if (wnum != rnum) { - LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - } - - return true; -#endif -} - -u64 GetSize(const std::string& filename) { - if (!Exists(filename)) { - LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); - return 0; - } - - if (IsDirectory(filename)) { - LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); - return 0; - } - - struct stat buf; -#ifdef _WIN32 - if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0) -#else - if (stat(filename.c_str(), &buf) == 0) -#endif - { - LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size); - return buf.st_size; - } - - LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg()); - return 0; -} - -u64 GetSize(const int fd) { - struct stat buf; - if (fstat(fd, &buf) != 0) { - LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg()); - return 0; - } - return buf.st_size; -} - -u64 GetSize(FILE* f) { - // can't use off_t here because it can be 32-bit - u64 pos = ftello(f); - if (fseeko(f, 0, SEEK_END) != 0) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg()); - return 0; - } - u64 size = ftello(f); - if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) { - LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", fmt::ptr(f), GetLastErrorMsg()); - return 0; - } - return size; -} - -bool CreateEmptyFile(const std::string& filename) { - LOG_TRACE(Common_Filesystem, "{}", filename); - - if (!IOFile(filename, "wb").IsOpen()) { - LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); - return false; - } - - return true; -} - -bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, - DirectoryEntryCallable callback) { - LOG_TRACE(Common_Filesystem, "directory {}", directory); - - // How many files + directories we found - u64 found_entries = 0; - - // Save the status of callback function - bool callback_error = false; - -#ifdef _WIN32 - // Find the first file in the directory. - WIN32_FIND_DATAW ffd; - - HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd); - if (handle_find == INVALID_HANDLE_VALUE) { - FindClose(handle_find); - return false; - } - // windows loop - do { - const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName)); -#else - DIR* dirp = opendir(directory.c_str()); - if (!dirp) - return false; - - // non windows loop - while (struct dirent* result = readdir(dirp)) { - const std::string virtual_name(result->d_name); -#endif - - if (virtual_name == "." || virtual_name == "..") - continue; - - u64 ret_entries = 0; - if (!callback(&ret_entries, directory, virtual_name)) { - callback_error = true; - break; - } - found_entries += ret_entries; - -#ifdef _WIN32 - } while (FindNextFileW(handle_find, &ffd) != 0); - FindClose(handle_find); -#else - } - closedir(dirp); -#endif - - if (callback_error) - return false; - - // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it - if (num_entries_out != nullptr) - *num_entries_out = found_entries; - return true; -} - -u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, - unsigned int recursion) { - const auto callback = [recursion, &parent_entry](u64* num_entries_out, - const std::string& directory, - const std::string& virtual_name) -> bool { - FSTEntry entry; - entry.virtualName = virtual_name; - entry.physicalName = directory + DIR_SEP + virtual_name; - - if (IsDirectory(entry.physicalName)) { - entry.isDirectory = true; - // is a directory, lets go inside if we didn't recurse to often - if (recursion > 0) { - entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1); - *num_entries_out += entry.size; - } else { - entry.size = 0; - } - } else { // is a file - entry.isDirectory = false; - entry.size = GetSize(entry.physicalName); - } - (*num_entries_out)++; - - // Push into the tree - parent_entry.children.push_back(std::move(entry)); - return true; - }; - - u64 num_entries; - return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0; -} - -bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) { - const auto callback = [recursion](u64*, const std::string& directory, - const std::string& virtual_name) { - const std::string new_path = directory + DIR_SEP_CHR + virtual_name; - - if (IsDirectory(new_path)) { - if (recursion == 0) { - return false; - } - return DeleteDirRecursively(new_path, recursion - 1); - } - return Delete(new_path); - }; - - if (!ForeachDirectoryEntry(nullptr, directory, callback)) - return false; - - // Delete the outermost directory - DeleteDir(directory); - return true; -} - -void CopyDir([[maybe_unused]] const std::string& source_path, - [[maybe_unused]] const std::string& dest_path) { -#ifndef _WIN32 - if (source_path == dest_path) { - return; - } - if (!Exists(source_path)) { - return; - } - if (!Exists(dest_path)) { - CreateFullPath(dest_path); - } - - DIR* dirp = opendir(source_path.c_str()); - if (!dirp) { - return; - } - - while (struct dirent* result = readdir(dirp)) { - const std::string virtualName(result->d_name); - // check for "." and ".." - if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || - ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) { - continue; - } - - std::string source, dest; - source = source_path + virtualName; - dest = dest_path + virtualName; - if (IsDirectory(source)) { - source += '/'; - dest += '/'; - if (!Exists(dest)) { - CreateFullPath(dest); - } - CopyDir(source, dest); - } else if (!Exists(dest)) { - Copy(source, dest); - } - } - closedir(dirp); -#endif -} - -std::optional<std::string> GetCurrentDir() { -// Get the current working directory (getcwd uses malloc) -#ifdef _WIN32 - wchar_t* dir = _wgetcwd(nullptr, 0); - if (!dir) { -#else - char* dir = getcwd(nullptr, 0); - if (!dir) { -#endif - LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); - return std::nullopt; - } -#ifdef _WIN32 - std::string strDir = Common::UTF16ToUTF8(dir); -#else - std::string strDir = dir; -#endif - free(dir); - return strDir; -} - -bool SetCurrentDir(const std::string& directory) { -#ifdef _WIN32 - return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; -#else - return chdir(directory.c_str()) == 0; -#endif -} - -#if defined(__APPLE__) -std::string GetBundleDirectory() { - CFURLRef BundleRef; - char AppBundlePath[MAXPATHLEN]; - // Get the main bundle for the app - BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle); - CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath)); - CFRelease(BundleRef); - CFRelease(BundlePath); - - return AppBundlePath; -} -#endif - -#ifdef _WIN32 -const std::string& GetExeDirectory() { - static std::string exe_path; - if (exe_path.empty()) { - wchar_t wchar_exe_path[2048]; - GetModuleFileNameW(nullptr, wchar_exe_path, 2048); - exe_path = Common::UTF16ToUTF8(wchar_exe_path); - exe_path = exe_path.substr(0, exe_path.find_last_of('\\')); - } - return exe_path; -} - -std::string AppDataRoamingDirectory() { - PWSTR pw_local_path = nullptr; - // Only supported by Windows Vista or later - SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path); - std::string local_path = Common::UTF16ToUTF8(pw_local_path); - CoTaskMemFree(pw_local_path); - return local_path; -} -#else -/** - * @return The user’s home directory on POSIX systems - */ -static const std::string& GetHomeDirectory() { - static std::string home_path; - if (home_path.empty()) { - const char* envvar = getenv("HOME"); - if (envvar) { - home_path = envvar; - } else { - auto pw = getpwuid(getuid()); - ASSERT_MSG(pw, - "$HOME isn’t defined, and the current user can’t be found in /etc/passwd."); - home_path = pw->pw_dir; - } - } - return home_path; -} - -/** - * Follows the XDG Base Directory Specification to get a directory path - * @param envvar The XDG environment variable to get the value from - * @return The directory path - * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - */ -static const std::string GetUserDirectory(const std::string& envvar) { - const char* directory = getenv(envvar.c_str()); - - std::string user_dir; - if (directory) { - user_dir = directory; - } else { - std::string subdirectory; - if (envvar == "XDG_DATA_HOME") - subdirectory = DIR_SEP ".local" DIR_SEP "share"; - else if (envvar == "XDG_CONFIG_HOME") - subdirectory = DIR_SEP ".config"; - else if (envvar == "XDG_CACHE_HOME") - subdirectory = DIR_SEP ".cache"; - else - ASSERT_MSG(false, "Unknown XDG variable {}.", envvar); - user_dir = GetHomeDirectory() + subdirectory; - } - - ASSERT_MSG(!user_dir.empty(), "User directory {} mustn’t be empty.", envvar); - ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar); - - return user_dir; -} -#endif - -std::string GetSysDirectory() { - std::string sysDir; - -#if defined(__APPLE__) - sysDir = GetBundleDirectory(); - sysDir += DIR_SEP; - sysDir += SYSDATA_DIR; -#else - sysDir = SYSDATA_DIR; -#endif - sysDir += DIR_SEP; - - LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir); - return sysDir; -} - -const std::string& GetUserPath(UserPath path, const std::string& new_path) { - static std::unordered_map<UserPath, std::string> paths; - auto& user_path = paths[UserPath::UserDir]; - - // Set up all paths and files on the first run - if (user_path.empty()) { -#ifdef _WIN32 - user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; - if (!IsDirectory(user_path)) { - user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; - } else { - LOG_INFO(Common_Filesystem, "Using the local user directory"); - } - - paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); - paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); -#else - if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { - user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; - paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); - paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); - } else { - std::string data_dir = GetUserDirectory("XDG_DATA_HOME"); - std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME"); - std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME"); - - user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; - paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); - paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); - } -#endif - paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); - paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); - paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); - paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); - paths.emplace(UserPath::ScreenshotsDir, user_path + SCREENSHOTS_DIR DIR_SEP); - paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP); - paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); - paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); - // TODO: Put the logs in a better location for each OS - paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); - } - - if (!new_path.empty()) { - if (!IsDirectory(new_path)) { - LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path); - return paths[path]; - } else { - paths[path] = new_path; - } - - switch (path) { - case UserPath::RootDir: - user_path = paths[UserPath::RootDir] + DIR_SEP; - break; - case UserPath::UserDir: - user_path = paths[UserPath::RootDir] + DIR_SEP; - paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP; - paths[UserPath::CacheDir] = user_path + CACHE_DIR DIR_SEP; - paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP; - paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP; - break; - default: - break; - } - } - - return paths[path]; -} - -std::string GetHactoolConfigurationPath() { -#ifdef _WIN32 - PWSTR pw_local_path = nullptr; - if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK) - return ""; - std::string local_path = Common::UTF16ToUTF8(pw_local_path); - CoTaskMemFree(pw_local_path); - return local_path + "\\.switch"; -#else - return GetHomeDirectory() + "/.switch"; -#endif -} - -std::string GetNANDRegistrationDir(bool system) { - if (system) - return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/"; - return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/"; -} - -std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) { - return IOFile(filename, text_file ? "w" : "wb").WriteString(str); -} - -std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) { - IOFile file(filename, text_file ? "r" : "rb"); - - if (!file.IsOpen()) - return 0; - - str.resize(static_cast<u32>(file.GetSize())); - return file.ReadArray(&str[0], str.size()); -} - -void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, - std::array<char, 4>& extension) { - static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, "; - - // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces. - short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}}; - extension = {{' ', ' ', ' ', '\0'}}; - - auto point = filename.rfind('.'); - if (point == filename.size() - 1) { - point = filename.rfind('.', point); - } - - // Get short name. - int j = 0; - for (char letter : filename.substr(0, point)) { - if (forbidden_characters.find(letter, 0) != std::string::npos) { - continue; - } - if (j == 8) { - // TODO(Link Mauve): also do that for filenames containing a space. - // TODO(Link Mauve): handle multiple files having the same short name. - short_name[6] = '~'; - short_name[7] = '1'; - break; - } - short_name[j++] = static_cast<char>(std::toupper(letter)); - } - - // Get extension. - if (point != std::string::npos) { - j = 0; - for (char letter : filename.substr(point + 1, 3)) { - extension[j++] = static_cast<char>(std::toupper(letter)); - } - } -} - -std::vector<std::string> SplitPathComponents(std::string_view filename) { - std::string copy(filename); - std::replace(copy.begin(), copy.end(), '\\', '/'); - std::vector<std::string> out; - - std::stringstream stream(copy); - std::string item; - while (std::getline(stream, item, '/')) { - out.push_back(std::move(item)); - } - - return out; -} - -std::string_view GetParentPath(std::string_view path) { - const auto name_bck_index = path.rfind('\\'); - const auto name_fwd_index = path.rfind('/'); - std::size_t name_index; - - if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { - name_index = std::min(name_bck_index, name_fwd_index); - } else { - name_index = std::max(name_bck_index, name_fwd_index); - } - - return path.substr(0, name_index); -} - -std::string_view GetPathWithoutTop(std::string_view path) { - if (path.empty()) { - return path; - } - - while (path[0] == '\\' || path[0] == '/') { - path.remove_prefix(1); - if (path.empty()) { - return path; - } - } - - const auto name_bck_index = path.find('\\'); - const auto name_fwd_index = path.find('/'); - return path.substr(std::min(name_bck_index, name_fwd_index) + 1); -} - -std::string_view GetFilename(std::string_view path) { - const auto name_index = path.find_last_of("\\/"); - - if (name_index == std::string_view::npos) { - return {}; - } - - return path.substr(name_index + 1); -} - -std::string_view GetExtensionFromFilename(std::string_view name) { - const std::size_t index = name.rfind('.'); - - if (index == std::string_view::npos) { - return {}; - } - - return name.substr(index + 1); -} - -std::string_view RemoveTrailingSlash(std::string_view path) { - if (path.empty()) { - return path; - } - - if (path.back() == '\\' || path.back() == '/') { - path.remove_suffix(1); - return path; - } - - return path; -} - -std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { - std::string path(path_); - char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; - char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; - - if (directory_separator == DirectorySeparator::PlatformDefault) { -#ifdef _WIN32 - type1 = '/'; - type2 = '\\'; -#endif - } - - std::replace(path.begin(), path.end(), type1, type2); - - auto start = path.begin(); -#ifdef _WIN32 - // allow network paths which start with a double backslash (e.g. \\server\share) - if (start != path.end()) - ++start; -#endif - path.erase(std::unique(start, path.end(), - [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), - path.end()); - return std::string(RemoveTrailingSlash(path)); -} - -IOFile::IOFile() = default; - -IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { - void(Open(filename, openmode, flags)); -} - -IOFile::~IOFile() { - Close(); -} - -IOFile::IOFile(IOFile&& other) noexcept { - Swap(other); -} - -IOFile& IOFile::operator=(IOFile&& other) noexcept { - Swap(other); - return *this; -} - -void IOFile::Swap(IOFile& other) noexcept { - std::swap(m_file, other.m_file); -} - -bool IOFile::Open(const std::string& filename, const char openmode[], int flags) { - Close(); - bool m_good; -#ifdef _WIN32 - if (flags != 0) { - m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(), - Common::UTF8ToUTF16W(openmode).c_str(), flags); - m_good = m_file != nullptr; - } else { - m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), - Common::UTF8ToUTF16W(openmode).c_str()) == 0; - } -#else - m_file = std::fopen(filename.c_str(), openmode); - m_good = m_file != nullptr; -#endif - - return m_good; -} - -bool IOFile::Close() { - if (!IsOpen() || 0 != std::fclose(m_file)) { - return false; - } - - m_file = nullptr; - return true; -} - -u64 IOFile::GetSize() const { - if (IsOpen()) { - return FS::GetSize(m_file); - } - return 0; -} - -bool IOFile::Seek(s64 off, int origin) const { - return IsOpen() && 0 == fseeko(m_file, off, origin); -} - -u64 IOFile::Tell() const { - if (IsOpen()) { - return ftello(m_file); - } - return std::numeric_limits<u64>::max(); -} - -bool IOFile::Flush() { - return IsOpen() && 0 == std::fflush(m_file); -} - -std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) const { - if (!IsOpen()) { - return std::numeric_limits<std::size_t>::max(); - } - - if (length == 0) { - return 0; - } - - DEBUG_ASSERT(data != nullptr); - - return std::fread(data, data_size, length, m_file); -} - -std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) { - if (!IsOpen()) { - return std::numeric_limits<std::size_t>::max(); - } - - if (length == 0) { - return 0; - } - - DEBUG_ASSERT(data != nullptr); - - return std::fwrite(data, data_size, length, m_file); -} - -bool IOFile::Resize(u64 size) { - return IsOpen() && 0 == -#ifdef _WIN32 - // ector: _chsize sucks, not 64-bit safe - // F|RES: changed to _chsize_s. i think it is 64-bit safe - _chsize_s(_fileno(m_file), size) -#else - // TODO: handle 64bit and growing - ftruncate(fileno(m_file), size) -#endif - ; -} - -} // 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 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <cstdio> -#include <fstream> -#include <functional> -#include <limits> -#include <optional> -#include <string> -#include <string_view> -#include <type_traits> -#include <vector> -#include "common/common_types.h" -#ifdef _MSC_VER -#include "common/string_util.h" -#endif - -namespace Common::FS { - -// User paths for GetUserPath -enum class UserPath { - CacheDir, - ConfigDir, - KeysDir, - LogDir, - NANDDir, - RootDir, - SDMCDir, - LoadDir, - DumpDir, - ScreenshotsDir, - ShaderDir, - SysDataDir, - UserDir, -}; - -// FileSystem tree node/ -struct FSTEntry { - bool isDirectory; - u64 size; // file length or number of entries from children - std::string physicalName; // name on disk - std::string virtualName; // name in FST names table - std::vector<FSTEntry> children; -}; - -// Returns true if file filename exists -[[nodiscard]] bool Exists(const std::string& filename); - -// Returns true if filename is a directory -[[nodiscard]] bool IsDirectory(const std::string& filename); - -// Returns the size of filename (64bit) -[[nodiscard]] u64 GetSize(const std::string& filename); - -// Overloaded GetSize, accepts file descriptor -[[nodiscard]] u64 GetSize(int fd); - -// Overloaded GetSize, accepts FILE* -[[nodiscard]] u64 GetSize(FILE* f); - -// Returns true if successful, or path already exists. -bool CreateDir(const std::string& filename); - -// Creates the full path of fullPath returns true on success -bool CreateFullPath(const std::string& fullPath); - -// Deletes a given filename, return true on success -// Doesn't supports deleting a directory -bool Delete(const std::string& filename); - -// Deletes a directory filename, returns true on success -bool DeleteDir(const std::string& filename); - -// renames file srcFilename to destFilename, returns true on success -bool Rename(const std::string& srcFilename, const std::string& destFilename); - -// copies file srcFilename to destFilename, returns true on success -bool Copy(const std::string& srcFilename, const std::string& destFilename); - -// creates an empty file filename, returns true on success -bool CreateEmptyFile(const std::string& filename); - -/** - * @param num_entries_out to be assigned by the callable with the number of iterated directory - * entries, never null - * @param directory the path to the enclosing directory - * @param virtual_name the entry name, without any preceding directory info - * @return whether handling the entry succeeded - */ -using DirectoryEntryCallable = std::function<bool( - u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>; - -/** - * Scans a directory, calling the callback for each file/directory contained within. - * If the callback returns failure, scanning halts and this function returns failure as well - * @param num_entries_out assigned by the function with the number of iterated directory entries, - * can be null - * @param directory the directory to scan - * @param callback The callback which will be called for each entry - * @return whether scanning the directory succeeded - */ -bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, - DirectoryEntryCallable callback); - -/** - * Scans the directory tree, storing the results. - * @param directory the parent directory to start scanning from - * @param parent_entry FSTEntry where the filesystem tree results will be stored. - * @param recursion Number of children directories to read before giving up. - * @return the total number of files/directories found - */ -u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, - unsigned int recursion = 0); - -// deletes the given directory and anything under it. Returns true on success. -bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); - -// Returns the current directory -[[nodiscard]] std::optional<std::string> GetCurrentDir(); - -// Create directory and copy contents (does not overwrite existing files) -void CopyDir(const std::string& source_path, const std::string& dest_path); - -// Set the current directory to given directory -bool SetCurrentDir(const std::string& directory); - -// Returns a pointer to a string with a yuzu data dir in the user's home -// directory. To be used in "multi-user" mode (that is, installed). -const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); - -[[nodiscard]] std::string GetHactoolConfigurationPath(); - -[[nodiscard]] std::string GetNANDRegistrationDir(bool system = false); - -// Returns the path to where the sys file are -[[nodiscard]] std::string GetSysDirectory(); - -#ifdef __APPLE__ -[[nodiscard]] std::string GetBundleDirectory(); -#endif - -#ifdef _WIN32 -[[nodiscard]] const std::string& GetExeDirectory(); -[[nodiscard]] std::string AppDataRoamingDirectory(); -#endif - -std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str); - -std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str); - -/** - * Splits the filename into 8.3 format - * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename - * @param filename The normal filename to use - * @param short_name A 9-char array in which the short name will be written - * @param extension A 4-char array in which the extension will be written - */ -void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name, - std::array<char, 4>& extension); - -// Splits the path on '/' or '\' and put the components into a vector -// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } -[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); - -// Gets all of the text up to the last '/' or '\' in the path. -[[nodiscard]] std::string_view GetParentPath(std::string_view path); - -// Gets all of the text after the first '/' or '\' in the path. -[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); - -// Gets the filename of the path -[[nodiscard]] std::string_view GetFilename(std::string_view path); - -// Gets the extension of the filename -[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); - -// Removes the final '/' or '\' if one exists -[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); - -// Creates a new vector containing indices [first, last) from the original. -template <typename T> -[[nodiscard]] std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, - std::size_t last) { - if (first >= last) { - return {}; - } - last = std::min<std::size_t>(last, vector.size()); - return std::vector<T>(vector.begin() + first, vector.begin() + first + last); -} - -enum class DirectorySeparator { - ForwardSlash, - BackwardSlash, - PlatformDefault, -}; - -// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' -// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows -[[nodiscard]] std::string SanitizePath( - std::string_view path, - DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); - -// To deal with Windows being dumb at Unicode -template <typename T> -void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { -#ifdef _MSC_VER - fstream.open(Common::UTF8ToUTF16W(filename), openmode); -#else - fstream.open(filename, openmode); -#endif -} - -// simple wrapper for cstdlib file functions to -// hopefully will make error checking easier -// and make forgetting an fclose() harder -class IOFile : public NonCopyable { -public: - IOFile(); - // flags is used for windows specific file open mode flags, which - // allows yuzu to open the logs in shared write mode, so that the file - // isn't considered "locked" while yuzu is open and people can open the log file and view it - IOFile(const std::string& filename, const char openmode[], int flags = 0); - - ~IOFile(); - - IOFile(IOFile&& other) noexcept; - IOFile& operator=(IOFile&& other) noexcept; - - void Swap(IOFile& other) noexcept; - - bool Open(const std::string& filename, const char openmode[], int flags = 0); - bool Close(); - - template <typename T> - std::size_t ReadArray(T* data, std::size_t length) const { - static_assert(std::is_trivially_copyable_v<T>, - "Given array does not consist of trivially copyable objects"); - - return ReadImpl(data, length, sizeof(T)); - } - - template <typename T> - std::size_t WriteArray(const T* data, std::size_t length) { - static_assert(std::is_trivially_copyable_v<T>, - "Given array does not consist of trivially copyable objects"); - - return WriteImpl(data, length, sizeof(T)); - } - - template <typename T> - std::size_t ReadBytes(T* data, std::size_t length) const { - static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); - return ReadArray(reinterpret_cast<char*>(data), length); - } - - template <typename T> - std::size_t WriteBytes(const T* data, std::size_t length) { - static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); - return WriteArray(reinterpret_cast<const char*>(data), length); - } - - template <typename T> - std::size_t WriteObject(const T& object) { - static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer"); - return WriteArray(&object, 1); - } - - std::size_t WriteString(std::string_view str) { - return WriteArray(str.data(), str.length()); - } - - [[nodiscard]] bool IsOpen() const { - return nullptr != m_file; - } - - bool Seek(s64 off, int origin) const; - [[nodiscard]] u64 Tell() const; - [[nodiscard]] u64 GetSize() const; - bool Resize(u64 size); - bool Flush(); - - // clear error state - void Clear() { - std::clearerr(m_file); - } - -private: - std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) const; - std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size); - - std::FILE* m_file = nullptr; -}; - -} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +#ifdef _WIN32 +#include <io.h> +#include <share.h> +#else +#include <unistd.h> +#endif + +#ifdef _MSC_VER +#define fileno _fileno +#define fseeko _fseeki64 +#define ftello _ftelli64 +#endif + +namespace Common::FS { + +namespace fs = std::filesystem; + +namespace { + +#ifdef _WIN32 + +/** + * Converts the file access mode and file type enums to a file access mode wide string. + * + * @param mode File access mode + * @param type File type + * + * @returns A pointer to a wide string representing the file access mode. + */ +[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) { + switch (type) { + case FileType::BinaryFile: + switch (mode) { + case FileAccessMode::Read: + return L"rb"; + case FileAccessMode::Write: + return L"wb"; + case FileAccessMode::Append: + return L"ab"; + case FileAccessMode::ReadWrite: + return L"r+b"; + case FileAccessMode::ReadAppend: + return L"a+b"; + } + break; + case FileType::TextFile: + switch (mode) { + case FileAccessMode::Read: + return L"r"; + case FileAccessMode::Write: + return L"w"; + case FileAccessMode::Append: + return L"a"; + case FileAccessMode::ReadWrite: + return L"r+"; + case FileAccessMode::ReadAppend: + return L"a+"; + } + break; + } + + return L""; +} + +/** + * Converts the file-share access flag enum to a Windows defined file-share access flag. + * + * @param flag File-share access flag + * + * @returns Windows defined file-share access flag. + */ +[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) { + switch (flag) { + case FileShareFlag::ShareNone: + default: + return _SH_DENYRW; + case FileShareFlag::ShareReadOnly: + return _SH_DENYWR; + case FileShareFlag::ShareWriteOnly: + return _SH_DENYRD; + case FileShareFlag::ShareReadWrite: + return _SH_DENYNO; + } +} + +#else + +/** + * Converts the file access mode and file type enums to a file access mode string. + * + * @param mode File access mode + * @param type File type + * + * @returns A pointer to a string representing the file access mode. + */ +[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) { + switch (type) { + case FileType::BinaryFile: + switch (mode) { + case FileAccessMode::Read: + return "rb"; + case FileAccessMode::Write: + return "wb"; + case FileAccessMode::Append: + return "ab"; + case FileAccessMode::ReadWrite: + return "r+b"; + case FileAccessMode::ReadAppend: + return "a+b"; + } + break; + case FileType::TextFile: + switch (mode) { + case FileAccessMode::Read: + return "r"; + case FileAccessMode::Write: + return "w"; + case FileAccessMode::Append: + return "a"; + case FileAccessMode::ReadWrite: + return "r+"; + case FileAccessMode::ReadAppend: + return "a+"; + } + break; + } + + return ""; +} + +#endif + +/** + * Converts the seek origin enum to a seek origin integer. + * + * @param origin Seek origin + * + * @returns Seek origin integer. + */ +[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) { + switch (origin) { + case SeekOrigin::SetOrigin: + default: + return SEEK_SET; + case SeekOrigin::CurrentPosition: + return SEEK_CUR; + case SeekOrigin::End: + return SEEK_END; + } +} + +} // Anonymous namespace + +std::string ReadStringFromFile(const std::filesystem::path& path, FileType type) { + if (!IsFile(path)) { + return ""; + } + + IOFile io_file{path, FileAccessMode::Read, type}; + + return io_file.ReadString(io_file.GetSize()); +} + +size_t WriteStringToFile(const std::filesystem::path& path, FileType type, + std::string_view string) { + if (!IsFile(path)) { + return 0; + } + + IOFile io_file{path, FileAccessMode::Write, type}; + + return io_file.WriteString(string); +} + +size_t AppendStringToFile(const std::filesystem::path& path, FileType type, + std::string_view string) { + if (!Exists(path)) { + return WriteStringToFile(path, type, string); + } + + if (!IsFile(path)) { + return 0; + } + + IOFile io_file{path, FileAccessMode::Append, type}; + + return io_file.WriteString(string); +} + +IOFile::IOFile() = default; + +IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Open(path, mode, type, flag); +} + +IOFile::~IOFile() { + Close(); +} + +IOFile::IOFile(IOFile&& other) noexcept { + std::swap(file_path, other.file_path); + std::swap(file_access_mode, other.file_access_mode); + std::swap(file_type, other.file_type); + std::swap(file, other.file); +} + +IOFile& IOFile::operator=(IOFile&& other) noexcept { + std::swap(file_path, other.file_path); + std::swap(file_access_mode, other.file_access_mode); + std::swap(file_type, other.file_type); + std::swap(file, other.file); + return *this; +} + +fs::path IOFile::GetPath() const { + return file_path; +} + +FileAccessMode IOFile::GetAccessMode() const { + return file_access_mode; +} + +FileType IOFile::GetType() const { + return file_type; +} + +void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) { + Close(); + + file_path = path; + file_access_mode = mode; + file_type = type; + + errno = 0; + +#ifdef _WIN32 + if (flag != FileShareFlag::ShareNone) { + file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); + } else { + _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); + } +#else + file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); +#endif + + if (!IsOpen()) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + } +} + +void IOFile::Close() { + if (!IsOpen()) { + return; + } + + errno = 0; + + const auto close_result = std::fclose(file) == 0; + + if (!close_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + } + + file = nullptr; +} + +bool IOFile::IsOpen() const { + return file != nullptr; +} + +std::string IOFile::ReadString(size_t length) const { + std::vector<char> string_buffer(length); + + const auto chars_read = ReadSpan<char>(string_buffer); + const auto string_size = chars_read != length ? chars_read : length; + + return std::string{string_buffer.data(), string_size}; +} + +size_t IOFile::WriteString(std::span<const char> string) const { + return WriteSpan(string); +} + +bool IOFile::Flush() const { + if (!IsOpen()) { + return false; + } + + errno = 0; + + const auto flush_result = std::fflush(file) == 0; + + if (!flush_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + } + + return flush_result; +} + +bool IOFile::SetSize(u64 size) const { + if (!IsOpen()) { + return false; + } + + errno = 0; + +#ifdef _WIN32 + const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0; +#else + const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0; +#endif + + if (!set_size_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}", + PathToUTF8String(file_path), size, ec.message()); + } + + return set_size_result; +} + +u64 IOFile::GetSize() const { + if (!IsOpen()) { + return 0; + } + + std::error_code ec; + + const auto file_size = fs::file_size(file_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + return 0; + } + + return file_size; +} + +bool IOFile::Seek(s64 offset, SeekOrigin origin) const { + if (!IsOpen()) { + return false; + } + + errno = 0; + + const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0; + + if (!seek_result) { + const auto ec = std::error_code{errno, std::generic_category()}; + LOG_ERROR(Common_Filesystem, + "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}", + PathToUTF8String(file_path), offset, origin, ec.message()); + } + + return seek_result; +} + +s64 IOFile::Tell() const { + if (!IsOpen()) { + return 0; + } + + errno = 0; + + return ftello(file); +} + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstdio> +#include <filesystem> +#include <fstream> +#include <span> +#include <type_traits> +#include <vector> + +#include "common/concepts.h" +#include "common/fs/fs_types.h" +#include "common/fs/fs_util.h" + +namespace Common::FS { + +enum class SeekOrigin { + SetOrigin, // Seeks from the start of the file. + CurrentPosition, // Seeks from the current file pointer position. + End, // Seeks from the end of the file. +}; + +/** + * Opens a file stream at path with the specified open mode. + * + * @param file_stream Reference to file stream + * @param path Filesystem path + * @param open_mode File stream open mode + */ +template <typename FileStream> +void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path, + std::ios_base::openmode open_mode) { + file_stream.open(path, open_mode); +} + +#ifdef _WIN32 +template <typename FileStream, typename Path> +void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) { + if constexpr (IsChar<typename Path::value_type>) { + file_stream.open(ToU8String(path), open_mode); + } else { + file_stream.open(std::filesystem::path{path}, open_mode); + } +} +#endif + +/** + * Reads an entire file at path and returns a string of the contents read from the file. + * If the filesystem object at path is not a file, this function returns an empty string. + * + * @param path Filesystem path + * @param type File type + * + * @returns A string of the contents read from the file. + */ +[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) { + if constexpr (IsChar<typename Path::value_type>) { + return ReadStringFromFile(ToU8String(path), type); + } else { + return ReadStringFromFile(std::filesystem::path{path}, type); + } +} +#endif + +/** + * Writes a string to a file at path and returns the number of characters successfully written. + * If an file already exists at path, its contents will be erased. + * If the filesystem object at path is not a file, this function returns 0. + * + * @param path Filesystem path + * @param type File type + * + * @returns Number of characters successfully written. + */ +[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type, + std::string_view string); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) { + if constexpr (IsChar<typename Path::value_type>) { + return WriteStringToFile(ToU8String(path), type, string); + } else { + return WriteStringToFile(std::filesystem::path{path}, type, string); + } +} +#endif + +/** + * Appends a string to a file at path and returns the number of characters successfully written. + * If a file does not exist at path, WriteStringToFile is called instead. + * If the filesystem object at path is not a file, this function returns 0. + * + * @param path Filesystem path + * @param type File type + * + * @returns Number of characters successfully written. + */ +[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type, + std::string_view string); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) { + if constexpr (IsChar<typename Path::value_type>) { + return AppendStringToFile(ToU8String(path), type, string); + } else { + return AppendStringToFile(std::filesystem::path{path}, type, string); + } +} +#endif + +class IOFile final : NonCopyable { +public: + IOFile(); + + explicit IOFile(const std::string& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + explicit IOFile(std::string_view path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + /** + * An IOFile is a lightweight wrapper on C Library file operations. + * Automatically closes an open file on the destruction of an IOFile object. + * + * @param path Filesystem path + * @param mode File access mode + * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file + * @param flag (Windows only) File-share access flag, default is ShareReadOnly + */ + explicit IOFile(const std::filesystem::path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + + virtual ~IOFile(); + + IOFile(IOFile&& other) noexcept; + IOFile& operator=(IOFile&& other) noexcept; + + /** + * Gets the path of the file. + * + * @returns The path of the file. + */ + [[nodiscard]] std::filesystem::path GetPath() const; + + /** + * Gets the access mode of the file. + * + * @returns The access mode of the file. + */ + [[nodiscard]] FileAccessMode GetAccessMode() const; + + /** + * Gets the type of the file. + * + * @returns The type of the file. + */ + [[nodiscard]] FileType GetType() const; + + /** + * Opens a file at path with the specified file access mode. + * This function behaves differently depending on the FileAccessMode. + * These behaviors are documented in each enum value of FileAccessMode. + * + * @param path Filesystem path + * @param mode File access mode + * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file + * @param flag (Windows only) File-share access flag, default is ShareReadOnly + */ + void Open(const std::filesystem::path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + +#ifdef _WIN32 + template <typename Path> + [[nodiscard]] void Open(const Path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly) { + using ValueType = typename Path::value_type; + if constexpr (IsChar<ValueType>) { + Open(ToU8String(path), mode, type, flag); + } else { + Open(std::filesystem::path{path}, mode, type, flag); + } + } +#endif + + /// Closes the file if it is opened. + void Close(); + + /** + * Checks whether the file is open. + * Use this to check whether the calls to Open() or Close() succeeded. + * + * @returns True if the file is open, false otherwise. + */ + [[nodiscard]] bool IsOpen() const; + + /** + * Helper function which deduces the value type of a contiguous STL container used in ReadSpan. + * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls + * ReadObject and T must be a trivially copyable object. + * + * See ReadSpan for more details if T is a contiguous container. + * See ReadObject for more details if T is a trivially copyable object. + * + * @tparam T Contiguous container or trivially copyable object + * + * @param data Container of T::value_type data or reference to object + * + * @returns Count of T::value_type data or objects successfully read. + */ + template <typename T> + [[nodiscard]] size_t Read(T& data) const { + if constexpr (IsSTLContainer<T>) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v<ContiguousType>, + "Data type must be trivially copyable."); + return ReadSpan<ContiguousType>(data); + } else { + return ReadObject(data) ? 1 : 0; + } + } + + /** + * Helper function which deduces the value type of a contiguous STL container used in WriteSpan. + * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls + * WriteObject and T must be a trivially copyable object. + * + * See WriteSpan for more details if T is a contiguous container. + * See WriteObject for more details if T is a trivially copyable object. + * + * @tparam T Contiguous container or trivially copyable object + * + * @param data Container of T::value_type data or const reference to object + * + * @returns Count of T::value_type data or objects successfully written. + */ + template <typename T> + [[nodiscard]] size_t Write(const T& data) const { + if constexpr (IsSTLContainer<T>) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v<ContiguousType>, + "Data type must be trivially copyable."); + return WriteSpan<ContiguousType>(data); + } else { + static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); + return WriteObject(data) ? 1 : 0; + } + } + + /** + * Reads a span of T data from a file sequentially. + * This function reads from the current position of the file pointer and + * advances it by the (count of T * sizeof(T)) bytes successfully read. + * + * Failures occur when: + * - The file is not open + * - The opened file lacks read permissions + * - Attempting to read beyond the end-of-file + * + * @tparam T Data type + * + * @param data Span of T data + * + * @returns Count of T data successfully read. + */ + template <typename T> + [[nodiscard]] size_t ReadSpan(std::span<T> data) const { + static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return std::fread(data.data(), sizeof(T), data.size(), file); + } + + /** + * Writes a span of T data to a file sequentially. + * This function writes from the current position of the file pointer and + * advances it by the (count of T * sizeof(T)) bytes successfully written. + * + * Failures occur when: + * - The file is not open + * - The opened file lacks write permissions + * + * @tparam T Data type + * + * @param data Span of T data + * + * @returns Count of T data successfully written. + */ + template <typename T> + [[nodiscard]] size_t WriteSpan(std::span<const T> data) const { + static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return std::fwrite(data.data(), sizeof(T), data.size(), file); + } + + /** + * Reads a T object from a file sequentially. + * This function reads from the current position of the file pointer and + * advances it by the sizeof(T) bytes successfully read. + * + * Failures occur when: + * - The file is not open + * - The opened file lacks read permissions + * - Attempting to read beyond the end-of-file + * + * @tparam T Data type + * + * @param object Reference to object + * + * @returns True if the object is successfully read from the file, false otherwise. + */ + template <typename T> + [[nodiscard]] bool ReadObject(T& object) const { + static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); + + if (!IsOpen()) { + return false; + } + + return std::fread(&object, sizeof(T), 1, file) == 1; + } + + /** + * Writes a T object to a file sequentially. + * This function writes from the current position of the file pointer and + * advances it by the sizeof(T) bytes successfully written. + * + * Failures occur when: + * - The file is not open + * - The opened file lacks write permissions + * + * @tparam T Data type + * + * @param object Const reference to object + * + * @returns True if the object is successfully written to the file, false otherwise. + */ + template <typename T> + [[nodiscard]] bool WriteObject(const T& object) const { + static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object."); + + if (!IsOpen()) { + return false; + } + + return std::fwrite(&object, sizeof(T), 1, file) == 1; + } + + /** + * Specialized function to read a string of a given length from a file sequentially. + * This function writes from the current position of the file pointer and + * advances it by the number of characters successfully read. + * The size of the returned string may not match length if not all bytes are successfully read. + * + * @param length Length of the string + * + * @returns A string read from the file. + */ + [[nodiscard]] std::string ReadString(size_t length) const; + + /** + * Specialized function to write a string to a file sequentially. + * This function writes from the current position of the file pointer and + * advances it by the number of characters successfully written. + * + * @param string Span of const char backed std::string or std::string_view + * + * @returns Number of characters successfully written. + */ + [[nodiscard]] size_t WriteString(std::span<const char> string) const; + + /** + * Flushes any unwritten buffered data into the file. + * + * @returns True if the flush was successful, false otherwise. + */ + [[nodiscard]] bool Flush() const; + + /** + * Resizes the file to a given size. + * If the file is resized to a smaller size, the remainder of the file is discarded. + * If the file is resized to a larger size, the new area appears as if zero-filled. + * + * Failures occur when: + * - The file is not open + * + * @param size File size in bytes + * + * @returns True if the file resize succeeded, false otherwise. + */ + [[nodiscard]] bool SetSize(u64 size) const; + + /** + * Gets the size of the file. + * + * Failures occur when: + * - The file is not open + * + * @returns The file size in bytes of the file. Returns 0 on failure. + */ + [[nodiscard]] u64 GetSize() const; + + /** + * Moves the current position of the file pointer with the specified offset and seek origin. + * + * @param offset Offset from seek origin + * @param origin Seek origin + * + * @returns True if the file pointer has moved to the specified offset, false otherwise. + */ + [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const; + + /** + * Gets the current position of the file pointer. + * + * @returns The current position of the file pointer. + */ + [[nodiscard]] s64 Tell() const; + +private: + std::filesystem::path file_path; + FileAccessMode file_access_mode; + FileType file_type; + + std::FILE* file = nullptr; +}; + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +namespace Common::FS { + +namespace fs = std::filesystem; + +// File Operations + +bool NewFile(const fs::path& path, u64 size) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + PathToUTF8String(path)); + return false; + } + + if (Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} exists", PathToUTF8String(path)); + return false; + } + + IOFile io_file{path, FileAccessMode::Write}; + + if (!io_file.IsOpen()) { + LOG_ERROR(Common_Filesystem, "Failed to create a file at path={}", PathToUTF8String(path)); + return false; + } + + if (!io_file.SetSize(size)) { + LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={} to size={}", + PathToUTF8String(path), size); + return false; + } + + io_file.Close(); + + LOG_DEBUG(Common_Filesystem, "Successfully created a file at path={} with size={}", + PathToUTF8String(path), size); + + return true; +} + +bool RemoveFile(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsFile(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to remove the file at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the file at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RenameFile(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + LOG_ERROR(Common_Filesystem, + "One or both input path(s) is not valid, old_path={}, new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + PathToUTF8String(old_path)); + return false; + } + + if (!IsFile(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a file", + PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +std::shared_ptr<IOFile> FileOpen(const fs::path& path, FileAccessMode mode, FileType type, + FileShareFlag flag) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return nullptr; + } + + if (!IsFile(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a file", + PathToUTF8String(path)); + return nullptr; + } + + auto io_file = std::make_shared<IOFile>(path, mode, type, flag); + + if (!io_file->IsOpen()) { + io_file.reset(); + + LOG_ERROR(Common_Filesystem, + "Failed to open the file at path={} with mode={}, type={}, flag={}", + PathToUTF8String(path), mode, type, flag); + + return nullptr; + } + + LOG_DEBUG(Common_Filesystem, + "Successfully opened the file at path={} with mode={}, type={}, flag={}", + PathToUTF8String(path), mode, type, flag); + + return io_file; +} + +// Directory Operations + +bool CreateDir(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path.parent_path())) { + LOG_ERROR(Common_Filesystem, "Parent directory of path={} does not exist", + PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directory(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to create the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully created the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool CreateDirs(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (IsDir(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} exists and is a directory", + PathToUTF8String(path)); + return true; + } + + std::error_code ec; + + fs::create_directories(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to create the directories at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully created the directories at path={}", + PathToUTF8String(path)); + + return true; +} + +bool CreateParentDir(const fs::path& path) { + return CreateDir(path.parent_path()); +} + +bool CreateParentDirs(const fs::path& path) { + return CreateDirs(path.parent_path()); +} + +bool RemoveDir(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to remove the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RemoveDirRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + fs::remove_all(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove the directory and its contents at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully removed the directory and its contents at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RemoveDirContentsRecursively(const fs::path& path) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return false; + } + + if (!Exists(path)) { + LOG_DEBUG(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return true; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return false; + } + + std::error_code ec; + + for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to completely enumerate the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + break; + } + + fs::remove(entry.path(), ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove the filesystem object at path={}, ec_message={}", + PathToUTF8String(entry.path()), ec.message()); + break; + } + } + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to remove all the contents of the directory at path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, + "Successfully removed all the contents of the directory at path={}", + PathToUTF8String(path)); + + return true; +} + +bool RenameDir(const fs::path& old_path, const fs::path& new_path) { + if (!ValidatePath(old_path) || !ValidatePath(new_path)) { + LOG_ERROR(Common_Filesystem, + "One or both input path(s) is not valid, old_path={}, new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + return false; + } + + if (!Exists(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} does not exist", + PathToUTF8String(old_path)); + return false; + } + + if (!IsDir(old_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at old_path={} is not a directory", + PathToUTF8String(old_path)); + return false; + } + + if (Exists(new_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} exists", + PathToUTF8String(new_path)); + return false; + } + + std::error_code ec; + + fs::rename(old_path, new_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to rename the file from old_path={} to new_path={}, ec_message={}", + PathToUTF8String(old_path), PathToUTF8String(new_path), ec.message()); + return false; + } + + LOG_DEBUG(Common_Filesystem, "Successfully renamed the file from old_path={} to new_path={}", + PathToUTF8String(old_path), PathToUTF8String(new_path)); + + return true; +} + +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, + DirEntryFilter filter) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + for (const auto& entry : fs::directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + } + + if (callback_error || ec) { + LOG_ERROR(Common_Filesystem, + "Failed to visit all the directory entries of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return; + } + + LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + PathToUTF8String(path)); +} + +void IterateDirEntriesRecursively(const std::filesystem::path& path, + const DirEntryCallable& callback, DirEntryFilter filter) { + if (!ValidatePath(path)) { + LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); + return; + } + + if (!Exists(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", + PathToUTF8String(path)); + return; + } + + if (!IsDir(path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", + PathToUTF8String(path)); + return; + } + + bool callback_error = false; + + std::error_code ec; + + for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + if (ec) { + break; + } + + if (True(filter & DirEntryFilter::File) && + entry.status().type() == fs::file_type::regular) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + + if (True(filter & DirEntryFilter::Directory) && + entry.status().type() == fs::file_type::directory) { + if (!callback(entry.path())) { + callback_error = true; + break; + } + } + } + + if (callback_error || ec) { + LOG_ERROR(Common_Filesystem, + "Failed to visit all the directory entries of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return; + } + + LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", + PathToUTF8String(path)); +} + +// Generic Filesystem Operations + +bool Exists(const fs::path& path) { + return fs::exists(path); +} + +bool IsFile(const fs::path& path) { + return fs::is_regular_file(path); +} + +bool IsDir(const fs::path& path) { + return fs::is_directory(path); +} + +fs::path GetCurrentDir() { + std::error_code ec; + + const auto current_path = fs::current_path(ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to get the current path, ec_message={}", ec.message()); + return {}; + } + + return current_path; +} + +bool SetCurrentDir(const fs::path& path) { + std::error_code ec; + + fs::current_path(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to set the current path to path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return false; + } + + return true; +} + +fs::file_type GetEntryType(const fs::path& path) { + std::error_code ec; + + const auto file_status = fs::status(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the entry type of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return fs::file_type::not_found; + } + + return file_status.type(); +} + +u64 GetSize(const fs::path& path) { + std::error_code ec; + + const auto file_size = fs::file_size(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return file_size; +} + +u64 GetFreeSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the available free space of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.free; +} + +u64 GetTotalSpaceSize(const fs::path& path) { + std::error_code ec; + + const auto space_info = fs::space(path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the total capacity of path={}, ec_message={}", + PathToUTF8String(path), ec.message()); + return 0; + } + + return space_info.capacity; +} + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <filesystem> +#include <memory> + +#include "common/fs/fs_types.h" +#include "common/fs/fs_util.h" + +namespace Common::FS { + +class IOFile; + +// File Operations + +/** + * Creates a new file at path with the specified size. + * + * Failures occur when: + * - Input path is not valid + * - The input path's parent directory does not exist + * - Filesystem object at path exists + * - Filesystem at path is read only + * + * @param path Filesystem path + * @param size File size + * + * @returns True if the file creation succeeds, false otherwise. + */ +[[nodiscard]] bool NewFile(const std::filesystem::path& path, u64 size = 0); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool NewFile(const Path& path, u64 size = 0) { + if constexpr (IsChar<typename Path::value_type>) { + return NewFile(ToU8String(path), size); + } else { + return NewFile(std::filesystem::path{path}, size); + } +} +#endif + +/** + * Removes a file at path. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a file + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if file removal succeeds or file does not exist, false otherwise. + */ +[[nodiscard]] bool RemoveFile(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool RemoveFile(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return RemoveFile(ToU8String(path)); + } else { + return RemoveFile(std::filesystem::path{path}); + } +} +#endif + +/** + * Renames a file from old_path to new_path. + * + * Failures occur when: + * - One or both input path(s) is not valid + * - Filesystem object at old_path does not exist + * - Filesystem object at old_path is not a file + * - Filesystem object at new_path exists + * - Filesystem at either path is read only + * + * @param old_path Old filesystem path + * @param new_path New filesystem path + * + * @returns True if file rename succeeds, false otherwise. + */ +[[nodiscard]] bool RenameFile(const std::filesystem::path& old_path, + const std::filesystem::path& new_path); + +#ifdef _WIN32 +template <typename Path1, typename Path2> +[[nodiscard]] bool RenameFile(const Path1& old_path, const Path2& new_path) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { + return RenameFile(ToU8String(old_path), ToU8String(new_path)); + } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { + return RenameFile(ToU8String(old_path), new_path); + } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { + return RenameFile(old_path, ToU8String(new_path)); + } else { + return RenameFile(std::filesystem::path{old_path}, std::filesystem::path{new_path}); + } +} +#endif + +/** + * Opens a file at path with the specified file access mode. + * This function behaves differently depending on the FileAccessMode. + * These behaviors are documented in each enum value of FileAccessMode. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a file + * - The file is not opened + * + * @param path Filesystem path + * @param mode File access mode + * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file + * @param flag (Windows only) File-share access flag, default is ShareReadOnly + * + * @returns A shared pointer to the opened file. Returns nullptr on failure. + */ +[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const std::filesystem::path& path, + FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] std::shared_ptr<IOFile> FileOpen(const Path& path, FileAccessMode mode, + FileType type = FileType::BinaryFile, + FileShareFlag flag = FileShareFlag::ShareReadOnly) { + if constexpr (IsChar<typename Path::value_type>) { + return FileOpen(ToU8String(path), mode, type, flag); + } else { + return FileOpen(std::filesystem::path{path}, mode, type, flag); + } +} +#endif + +// Directory Operations + +/** + * Creates a directory at path. + * Note that this function will *always* assume that the input path is a directory. For example, + * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". + * If you intend to create the parent directory of a file, use CreateParentDir instead. + * + * Failures occur when: + * - Input path is not valid + * - The input path's parent directory does not exist + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool CreateDir(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return CreateDir(ToU8String(path)); + } else { + return CreateDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Recursively creates a directory at path. + * Note that this function will *always* assume that the input path is a directory. For example, + * if the input path is /path/to/directory/file.txt, it will create a directory called "file.txt". + * If you intend to create the parent directory of a file, use CreateParentDirs instead. + * Unlike CreateDir, this creates all of input path's parent directories if they do not exist. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateDirs(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool CreateDirs(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return CreateDirs(ToU8String(path)); + } else { + return CreateDirs(std::filesystem::path{path}); + } +} +#endif + +/** + * Creates the parent directory of a given path. + * This function calls CreateDir(path.parent_path()), see CreateDir for more details. + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateParentDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool CreateParentDir(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return CreateParentDir(ToU8String(path)); + } else { + return CreateParentDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Recursively creates the parent directory of a given path. + * This function calls CreateDirs(path.parent_path()), see CreateDirs for more details. + * + * @param path Filesystem path + * + * @returns True if directory creation succeeds or directory already exists, false otherwise. + */ +[[nodiscard]] bool CreateParentDirs(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool CreateParentDirs(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return CreateParentDirs(ToU8String(path)); + } else { + return CreateParentDirs(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes a directory at path. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - The given directory is not empty + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if directory removal succeeds or directory does not exist, false otherwise. + */ +[[nodiscard]] bool RemoveDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool RemoveDir(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return RemoveDir(ToU8String(path)); + } else { + return RemoveDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes all the contents within the given directory and removes the directory itself. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if the directory and all of its contents are removed successfully, false otherwise. + */ +[[nodiscard]] bool RemoveDirRecursively(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool RemoveDirRecursively(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return RemoveDirRecursively(ToU8String(path)); + } else { + return RemoveDirRecursively(std::filesystem::path{path}); + } +} +#endif + +/** + * Removes all the contents within the given directory without removing the directory itself. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * - Filesystem at path is read only + * + * @param path Filesystem path + * + * @returns True if all of the directory's contents are removed successfully, false otherwise. + */ +[[nodiscard]] bool RemoveDirContentsRecursively(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool RemoveDirContentsRecursively(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return RemoveDirContentsRecursively(ToU8String(path)); + } else { + return RemoveDirContentsRecursively(std::filesystem::path{path}); + } +} +#endif + +/** + * Renames a directory from old_path to new_path. + * + * Failures occur when: + * - One or both input path(s) is not valid + * - Filesystem object at old_path does not exist + * - Filesystem object at old_path is not a directory + * - Filesystem object at new_path exists + * - Filesystem at either path is read only + * + * @param old_path Old filesystem path + * @param new_path New filesystem path + * + * @returns True if directory rename succeeds, false otherwise. + */ +[[nodiscard]] bool RenameDir(const std::filesystem::path& old_path, + const std::filesystem::path& new_path); + +#ifdef _WIN32 +template <typename Path1, typename Path2> +[[nodiscard]] bool RenameDir(const Path1& old_path, const Path2& new_path) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { + return RenameDir(ToU8String(old_path), ToU8String(new_path)); + } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { + return RenameDir(ToU8String(old_path), new_path); + } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { + return RenameDir(old_path, ToU8String(new_path)); + } else { + return RenameDir(std::filesystem::path{old_path}, std::filesystem::path{new_path}); + } +} +#endif + +/** + * Iterates over the directory entries of a given directory. + * This does not iterate over the sub-directories of the given directory. + * The DirEntryCallable callback is called for each visited directory entry. + * A filter can be set to control which directory entries are visited based on their type. + * By default, both files and directories are visited. + * If the callback returns false or there is an error, the iteration is immediately halted. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path is not a directory + * + * @param path Filesystem path + * @param callback Callback to be called for each visited directory entry + * @param filter Directory entry type filter + */ +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All); + +#ifdef _WIN32 +template <typename Path> +void IterateDirEntries(const Path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All) { + if constexpr (IsChar<typename Path::value_type>) { + IterateDirEntries(ToU8String(path), callback, filter); + } else { + IterateDirEntries(std::filesystem::path{path}, callback, filter); + } +} +#endif + +/** + * Iterates over the directory entries of a given directory and its sub-directories. + * The DirEntryCallable callback is called for each visited directory entry. + * A filter can be set to control which directory entries are visited based on their type. + * By default, both files and directories are visited. + * If the callback returns false or there is an error, the iteration is immediately halted. + * + * Failures occur when: + * - Input path is not valid + * - Filesystem object at path does not exist + * - Filesystem object at path is not a directory + * + * @param path Filesystem path + * @param callback Callback to be called for each visited directory entry + * @param filter Directory entry type filter + */ +void IterateDirEntriesRecursively(const std::filesystem::path& path, + const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All); + +#ifdef _WIN32 +template <typename Path> +void IterateDirEntriesRecursively(const Path& path, const DirEntryCallable& callback, + DirEntryFilter filter = DirEntryFilter::All) { + if constexpr (IsChar<typename Path::value_type>) { + IterateDirEntriesRecursively(ToU8String(path), callback, filter); + } else { + IterateDirEntriesRecursively(std::filesystem::path{path}, callback, filter); + } +} +#endif + +// Generic Filesystem Operations + +/** + * Returns whether a filesystem object at path exists. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path exists, false otherwise. + */ +[[nodiscard]] bool Exists(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool Exists(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return Exists(ToU8String(path)); + } else { + return Exists(std::filesystem::path{path}); + } +} +#endif + +/** + * Returns whether a filesystem object at path is a file. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path is a file, false otherwise. + */ +[[nodiscard]] bool IsFile(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool IsFile(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return IsFile(ToU8String(path)); + } else { + return IsFile(std::filesystem::path{path}); + } +} +#endif + +/** + * Returns whether a filesystem object at path is a directory. + * + * @param path Filesystem path + * + * @returns True if a filesystem object at path is a directory, false otherwise. + */ +[[nodiscard]] bool IsDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool IsDir(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return IsDir(ToU8String(path)); + } else { + return IsDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the current working directory. + * + * @returns The current working directory. Returns an empty path on failure. + */ +[[nodiscard]] std::filesystem::path GetCurrentDir(); + +/** + * Sets the current working directory to path. + * + * @returns True if the current working directory is successfully set, false otherwise. + */ +[[nodiscard]] bool SetCurrentDir(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool SetCurrentDir(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return SetCurrentDir(ToU8String(path)); + } else { + return SetCurrentDir(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the entry type of the filesystem object at path. + * + * @param path Filesystem path + * + * @returns The entry type of the filesystem object. Returns file_type::not_found on failure. + */ +[[nodiscard]] std::filesystem::file_type GetEntryType(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] std::filesystem::file_type GetEntryType(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return GetEntryType(ToU8String(path)); + } else { + return GetEntryType(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the size of the filesystem object at path. + * + * @param path Filesystem path + * + * @returns The size in bytes of the filesystem object. Returns 0 on failure. + */ +[[nodiscard]] u64 GetSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] u64 GetSize(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return GetSize(ToU8String(path)); + } else { + return GetSize(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the free space size of the filesystem at path. + * + * @param path Filesystem path + * + * @returns The free space size in bytes of the filesystem at path. Returns 0 on failure. + */ +[[nodiscard]] u64 GetFreeSpaceSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] u64 GetFreeSpaceSize(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return GetFreeSpaceSize(ToU8String(path)); + } else { + return GetFreeSpaceSize(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the total capacity of the filesystem at path. + * + * @param path Filesystem path + * + * @returns The total capacity in bytes of the filesystem at path. Returns 0 on failure. + */ +[[nodiscard]] u64 GetTotalSpaceSize(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] u64 GetTotalSpaceSize(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return GetTotalSpaceSize(ToU8String(path)); + } else { + return GetTotalSpaceSize(std::filesystem::path{path}); + } +} +#endif + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +// yuzu data directories + +#define YUZU_DIR "yuzu" +#define PORTABLE_DIR "user" + +// Sub-directories contained within a yuzu data directory + +#define CACHE_DIR "cache" +#define CONFIG_DIR "config" +#define DUMP_DIR "dump" +#define KEYS_DIR "keys" +#define LOAD_DIR "load" +#define LOG_DIR "log" +#define NAND_DIR "nand" +#define SCREENSHOTS_DIR "screenshots" +#define SDMC_DIR "sdmc" +#define SHADER_DIR "shader" + +// yuzu-specific files + +#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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Common::FS { + +enum class FileAccessMode { + /** + * If the file at path exists, it opens the file for reading. + * If the file at path does not exist, it fails to open the file. + */ + Read = 1 << 0, + /** + * If the file at path exists, the existing contents of the file are erased. + * The empty file is then opened for writing. + * If the file at path does not exist, it creates and opens a new empty file for writing. + */ + Write = 1 << 1, + /** + * If the file at path exists, it opens the file for reading and writing. + * If the file at path does not exist, it fails to open the file. + */ + ReadWrite = Read | Write, + /** + * If the file at path exists, it opens the file for appending. + * If the file at path does not exist, it creates and opens a new empty file for appending. + */ + Append = 1 << 2, + /** + * If the file at path exists, it opens the file for both reading and appending. + * If the file at path does not exist, it creates and opens a new empty file for both + * reading and appending. + */ + ReadAppend = Read | Append, +}; + +enum class FileType { + BinaryFile, + TextFile, +}; + +enum class FileShareFlag { + ShareNone, // Provides exclusive access to the file. + ShareReadOnly, // Provides read only shared access to the file. + ShareWriteOnly, // Provides write only shared access to the file. + ShareReadWrite, // Provides read and write shared access to the file. +}; + +enum class DirEntryFilter { + File = 1 << 0, + Directory = 1 << 1, + All = File | Directory, +}; +DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); + +/** + * A callback function which takes in the path of a directory entry. + * + * @param path The path of a directory entry + * + * @returns A boolean value. + * Return true to indicate whether the callback is successful, false otherwise. + */ +using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/fs/fs_util.h" + +namespace Common::FS { + +std::u8string ToU8String(std::string_view utf8_string) { + return std::u8string{utf8_string.begin(), utf8_string.end()}; +} + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <concepts> +#include <string> +#include <string_view> + +namespace Common::FS { + +template <typename T> +concept IsChar = std::same_as<T, char>; + +/** + * Converts a UTF-8 encoded std::string or std::string_view to a std::u8string. + * + * @param utf8_string UTF-8 encoded string + * + * @returns UTF-8 encoded std::u8string. + */ +[[nodiscard]] std::u8string ToU8String(std::string_view utf8_string); + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <unordered_map> + +#include "common/fs/fs.h" +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" + +#ifdef _WIN32 +#include <shlobj.h> // Used in GetExeDirectory() +#else +#include <cstdlib> // Used in Get(Home/Data)Directory() +#include <pwd.h> // Used in GetHomeDirectory() +#include <sys/types.h> // Used in GetHomeDirectory() +#include <unistd.h> // Used in GetDataDirectory() +#endif + +#ifdef __APPLE__ +#include <sys/param.h> // Used in GetBundleDirectory() + +// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just +// ignore them if we're not using clang. The macro is only used to prevent linking against +// functions that don't exist on older versions of macOS, and the worst case scenario is a linker +// error, so this is perfectly safe, just inconvenient. +#ifndef __clang__ +#define availability(...) +#endif +#include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory() +#include <CoreFoundation/CFString.h> // Used in GetBundleDirectory() +#include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory() +#ifdef availability +#undef availability +#endif +#endif + +#ifndef MAX_PATH +#ifdef _WIN32 +// This is the maximum number of UTF-16 code units permissible in Windows file paths +#define MAX_PATH 260 +#else +// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths +#define MAX_PATH 1024 +#endif +#endif + +namespace Common::FS { + +namespace fs = std::filesystem; + +/** + * The PathManagerImpl is a singleton allowing to manage the mapping of + * YuzuPath enums to real filesystem paths. + * This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl. + * These are used by GetYuzuPath and SetYuzuPath respectively to get or modify + * the path mapped by the YuzuPath enum. + */ +class PathManagerImpl { +public: + static PathManagerImpl& GetInstance() { + static PathManagerImpl path_manager_impl; + + return path_manager_impl; + } + + PathManagerImpl(const PathManagerImpl&) = delete; + PathManagerImpl& operator=(const PathManagerImpl&) = delete; + + PathManagerImpl(PathManagerImpl&&) = delete; + PathManagerImpl& operator=(PathManagerImpl&&) = delete; + + [[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) { + return yuzu_paths.at(yuzu_path); + } + + void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) { + yuzu_paths.insert_or_assign(yuzu_path, new_path); + } + +private: + PathManagerImpl() { +#ifdef _WIN32 + auto yuzu_path = GetExeDirectory() / PORTABLE_DIR; + + if (!IsDir(yuzu_path)) { + yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; + } + + GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); + GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); + GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); +#else + auto yuzu_path = GetCurrentDir() / PORTABLE_DIR; + + if (Exists(yuzu_path) && IsDir(yuzu_path)) { + GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); + GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path / CACHE_DIR); + GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path / CONFIG_DIR); + } else { + yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR; + + GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path); + GenerateYuzuPath(YuzuPath::CacheDir, GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR); + GenerateYuzuPath(YuzuPath::ConfigDir, GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR); + } +#endif + + GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); + GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR); + GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); + GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); + GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); + GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); + GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); + GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); + } + + ~PathManagerImpl() = default; + + void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { + void(FS::CreateDir(new_path)); + + SetYuzuPathImpl(yuzu_path, new_path); + } + + std::unordered_map<YuzuPath, fs::path> yuzu_paths; +}; + +std::string PathToUTF8String(const fs::path& path) { + const auto utf8_string = path.u8string(); + + return std::string{utf8_string.begin(), utf8_string.end()}; +} + +bool ValidatePath(const fs::path& path) { + if (path.empty()) { + LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path)); + return false; + } + +#ifdef _WIN32 + if (path.u16string().size() >= MAX_PATH) { + LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); + return false; + } +#else + if (path.u8string().size() >= MAX_PATH) { + LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path)); + return false; + } +#endif + + return true; +} + +fs::path ConcatPath(const fs::path& first, const fs::path& second) { + const bool second_has_dir_sep = IsDirSeparator(second.u8string().front()); + + if (!second_has_dir_sep) { + return (first / second).lexically_normal(); + } + + fs::path concat_path = first; + concat_path += second; + + return concat_path.lexically_normal(); +} + +fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) { + const auto concatenated_path = ConcatPath(base, offset); + + if (!IsPathSandboxed(base, concatenated_path)) { + return base; + } + + return concatenated_path; +} + +bool IsPathSandboxed(const fs::path& base, const fs::path& path) { + const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string(); + const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string(); + + if (path_string.size() < base_string.size()) { + return false; + } + + return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0; +} + +bool IsDirSeparator(char character) { + return character == '/' || character == '\\'; +} + +bool IsDirSeparator(char8_t character) { + return character == u8'/' || character == u8'\\'; +} + +fs::path RemoveTrailingSeparators(const fs::path& path) { + if (path.empty()) { + return path; + } + + auto string_path = path.u8string(); + + while (IsDirSeparator(string_path.back())) { + string_path.pop_back(); + } + + return fs::path{string_path}; +} + +const fs::path& GetYuzuPath(YuzuPath yuzu_path) { + return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); +} + +std::string GetYuzuPathString(YuzuPath yuzu_path) { + return PathToUTF8String(GetYuzuPath(yuzu_path)); +} + +void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { + if (!FS::IsDir(new_path)) { + LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory", + PathToUTF8String(new_path)); + return; + } + + PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path); +} + +#ifdef _WIN32 + +fs::path GetExeDirectory() { + wchar_t exe_path[MAX_PATH]; + + GetModuleFileNameW(nullptr, exe_path, MAX_PATH); + + if (!exe_path) { + LOG_ERROR(Common_Filesystem, + "Failed to get the path to the executable of the current process"); + } + + return fs::path{exe_path}.parent_path(); +} + +fs::path GetAppDataRoamingDirectory() { + PWSTR appdata_roaming_path = nullptr; + + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path); + + auto fs_appdata_roaming_path = fs::path{appdata_roaming_path}; + + CoTaskMemFree(appdata_roaming_path); + + if (fs_appdata_roaming_path.empty()) { + LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory"); + } + + return fs_appdata_roaming_path; +} + +#else + +fs::path GetHomeDirectory() { + const char* home_env_var = getenv("HOME"); + + if (home_env_var) { + return fs::path{home_env_var}; + } + + LOG_INFO(Common_Filesystem, + "$HOME is not defined in the environment variables, " + "attempting to query passwd to get the home path of the current user"); + + const auto* pw = getpwuid(getuid()); + + if (!pw) { + LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user"); + return {}; + } + + return fs::path{pw->pw_dir}; +} + +fs::path GetDataDirectory(const std::string& env_name) { + const char* data_env_var = getenv(env_name.c_str()); + + if (data_env_var) { + return fs::path{data_env_var}; + } + + if (env_name == "XDG_DATA_HOME") { + return GetHomeDirectory() / ".local/share"; + } else if (env_name == "XDG_CACHE_HOME") { + return GetHomeDirectory() / ".cache"; + } else if (env_name == "XDG_CONFIG_HOME") { + return GetHomeDirectory() / ".config"; + } + + return {}; +} + +#endif + +#ifdef __APPLE__ + +fs::path GetBundleDirectory() { + char app_bundle_path[MAXPATHLEN]; + + // Get the main bundle for the app + CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle); + + CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path)); + + CFRelease(bundle_ref); + CFRelease(bundle_path); + + return fs::path{app_bundle_path}; +} + +#endif + +// vvvvvvvvvv Deprecated vvvvvvvvvv // + +std::string_view RemoveTrailingSlash(std::string_view path) { + if (path.empty()) { + return path; + } + + if (path.back() == '\\' || path.back() == '/') { + path.remove_suffix(1); + return path; + } + + return path; +} + +std::vector<std::string> SplitPathComponents(std::string_view filename) { + std::string copy(filename); + std::replace(copy.begin(), copy.end(), '\\', '/'); + std::vector<std::string> out; + + std::stringstream stream(copy); + std::string item; + while (std::getline(stream, item, '/')) { + out.push_back(std::move(item)); + } + + return out; +} + +std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { + std::string path(path_); + char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; + char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; + + if (directory_separator == DirectorySeparator::PlatformDefault) { +#ifdef _WIN32 + type1 = '/'; + type2 = '\\'; +#endif + } + + std::replace(path.begin(), path.end(), type1, type2); + + auto start = path.begin(); +#ifdef _WIN32 + // allow network paths which start with a double backslash (e.g. \\server\share) + if (start != path.end()) + ++start; +#endif + path.erase(std::unique(start, path.end(), + [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), + path.end()); + return std::string(RemoveTrailingSlash(path)); +} + +std::string_view GetParentPath(std::string_view path) { + const auto name_bck_index = path.rfind('\\'); + const auto name_fwd_index = path.rfind('/'); + std::size_t name_index; + + if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { + name_index = std::min(name_bck_index, name_fwd_index); + } else { + name_index = std::max(name_bck_index, name_fwd_index); + } + + return path.substr(0, name_index); +} + +std::string_view GetPathWithoutTop(std::string_view path) { + if (path.empty()) { + return path; + } + + while (path[0] == '\\' || path[0] == '/') { + path.remove_prefix(1); + if (path.empty()) { + return path; + } + } + + const auto name_bck_index = path.find('\\'); + const auto name_fwd_index = path.find('/'); + return path.substr(std::min(name_bck_index, name_fwd_index) + 1); +} + +std::string_view GetFilename(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + + if (name_index == std::string_view::npos) { + return {}; + } + + return path.substr(name_index + 1); +} + +std::string_view GetExtensionFromFilename(std::string_view name) { + const std::size_t index = name.rfind('.'); + + if (index == std::string_view::npos) { + return {}; + } + + return name.substr(index + 1); +} + +} // 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 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <filesystem> +#include <vector> + +#include "common/fs/fs_util.h" + +namespace Common::FS { + +enum class YuzuPath { + YuzuDir, // Where yuzu stores its data. + CacheDir, // Where cached filesystem data is stored. + ConfigDir, // Where config files are stored. + DumpDir, // Where dumped data is stored. + KeysDir, // Where key files are stored. + LoadDir, // Where cheat/mod files are stored. + LogDir, // Where log files are stored. + NANDDir, // Where the emulated NAND is stored. + ScreenshotsDir, // Where yuzu screenshots are stored. + SDMCDir, // Where the emulated SDMC is stored. + ShaderDir, // Where shaders are stored. +}; + +/** + * Converts a filesystem path to a UTF-8 encoded std::string. + * + * @param path Filesystem path + * + * @returns UTF-8 encoded std::string. + */ +[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path); + +/** + * Validates a given path. + * + * A given path is valid if it meets these conditions: + * - The path is not empty + * - The path is not too long + * + * @param path Filesystem path + * + * @returns True if the path is valid, false otherwise. + */ +[[nodiscard]] bool ValidatePath(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] bool ValidatePath(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return ValidatePath(ToU8String(path)); + } else { + return ValidatePath(std::filesystem::path{path}); + } +} +#endif + +/** + * Concatenates two filesystem paths together. + * + * This is needed since the following occurs when using std::filesystem::path's operator/: + * first: "/first/path" + * second: "/second/path" (Note that the second path has a directory separator in the front) + * first / second yields "/second/path" when the desired result is first/path/second/path + * + * @param first First filesystem path + * @param second Second filesystem path + * + * @returns A concatenated filesystem path. + */ +[[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first, + const std::filesystem::path& second); + +#ifdef _WIN32 +template <typename Path1, typename Path2> +[[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { + return ConcatPath(ToU8String(first), ToU8String(second)); + } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { + return ConcatPath(ToU8String(first), second); + } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { + return ConcatPath(first, ToU8String(second)); + } else { + return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second}); + } +} +#endif + +/** + * Safe variant of ConcatPath that takes in a base path and an offset path from the given base path. + * + * If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path, + * this will return the concatenated path. Otherwise this will return the base path. + * + * @param base Base filesystem path + * @param offset Offset filesystem path + * + * @returns A concatenated filesystem path if it is within the base path, + * returns the base path otherwise. + */ +[[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base, + const std::filesystem::path& offset); + +#ifdef _WIN32 +template <typename Path1, typename Path2> +[[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { + return ConcatPathSafe(ToU8String(base), ToU8String(offset)); + } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { + return ConcatPathSafe(ToU8String(base), offset); + } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { + return ConcatPathSafe(base, ToU8String(offset)); + } else { + return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset}); + } +} +#endif + +/** + * Checks whether a given path is sandboxed within a given base path. + * + * @param base Base filesystem path + * @param path Filesystem path + * + * @returns True if the given path is sandboxed within the given base path, false otherwise. + */ +[[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base, + const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path1, typename Path2> +[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) { + using ValueType1 = typename Path1::value_type; + using ValueType2 = typename Path2::value_type; + if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) { + return IsPathSandboxed(ToU8String(base), ToU8String(path)); + } else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) { + return IsPathSandboxed(ToU8String(base), path); + } else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) { + return IsPathSandboxed(base, ToU8String(path)); + } else { + return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path}); + } +} +#endif + +/** + * Checks if a character is a directory separator (either a forward slash or backslash). + * + * @param character Character + * + * @returns True if the character is a directory separator, false otherwise. + */ +[[nodiscard]] bool IsDirSeparator(char character); + +/** + * Checks if a character is a directory separator (either a forward slash or backslash). + * + * @param character Character + * + * @returns True if the character is a directory separator, false otherwise. + */ +[[nodiscard]] bool IsDirSeparator(char8_t character); + +/** + * Removes any trailing directory separators if they exist in the given path. + * + * @param path Filesystem path + * + * @returns The filesystem path without any trailing directory separators. + */ +[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) { + if constexpr (IsChar<typename Path::value_type>) { + return RemoveTrailingSeparators(ToU8String(path)); + } else { + return RemoveTrailingSeparators(std::filesystem::path{path}); + } +} +#endif + +/** + * Gets the filesystem path associated with the YuzuPath enum. + * + * @param yuzu_path YuzuPath enum + * + * @returns The filesystem path associated with the YuzuPath enum. + */ +[[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path); + +/** + * Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. + * + * @param yuzu_path YuzuPath enum + * + * @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string. + */ +[[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path); + +/** + * Sets a new filesystem path associated with the YuzuPath enum. + * If the filesystem object at new_path is not a directory, this function will not do anything. + * + * @param yuzu_path YuzuPath enum + * @param new_path New filesystem path + */ +void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path); + +#ifdef _WIN32 +template <typename Path> +[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { + if constexpr (IsChar<typename Path::value_type>) { + SetYuzuPath(yuzu_path, ToU8String(new_path)); + } else { + SetYuzuPath(yuzu_path, std::filesystem::path{new_path}); + } +} +#endif + +#ifdef _WIN32 + +/** + * Gets the path of the directory containing the executable of the current process. + * + * @returns The path of the directory containing the executable of the current process. + */ +[[nodiscard]] std::filesystem::path GetExeDirectory(); + +/** + * Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming). + * + * @returns The path of the current user's %APPDATA% directory. + */ +[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory(); + +#else + +/** + * Gets the path of the directory specified by the #HOME environment variable. + * If $HOME is not defined, it will attempt to query the user database in passwd instead. + * + * @returns The path of the current user's home directory. + */ +[[nodiscard]] std::filesystem::path GetHomeDirectory(); + +/** + * Gets the relevant paths for yuzu to store its data based on the given XDG environment variable. + * See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + * Defaults to $HOME/.local/share for main application data, + * $HOME/.cache for cached data, and $HOME/.config for configuration files. + * + * @param env_name XDG environment variable name + * + * @returns The path where yuzu should store its data. + */ +[[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name); + +#endif + +#ifdef __APPLE__ + +[[nodiscard]] std::filesystem::path GetBundleDirectory(); + +#endif + +// vvvvvvvvvv Deprecated vvvvvvvvvv // + +// Removes the final '/' or '\' if one exists +[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path); + +enum class DirectorySeparator { + ForwardSlash, + BackwardSlash, + PlatformDefault, +}; + +// Splits the path on '/' or '\' and put the components into a vector +// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } +[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); + +// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' +// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows +[[nodiscard]] std::string SanitizePath( + std::string_view path, + DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); + +// Gets all of the text up to the last '/' or '\' in the path. +[[nodiscard]] std::string_view GetParentPath(std::string_view path); + +// Gets all of the text after the first '/' or '\' in the path. +[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); + +// Gets the filename of the path +[[nodiscard]] std::string_view GetFilename(std::string_view path); + +// Gets the extension of the filename +[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name); + +} // 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 @@ #include <mutex> #include <thread> #include <vector> + #ifdef _WIN32 -#include <share.h> // For _SH_DENYWR #include <windows.h> // For OutputDebugStringW -#else -#define _SH_DENYWR 0 #endif + #include "common/assert.h" +#include "common/fs/fs.h" #include "common/logging/backend.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" @@ -148,19 +148,16 @@ void ColorConsoleBackend::Write(const Entry& entry) { PrintColoredMessage(entry); } -FileBackend::FileBackend(const std::string& filename) { - const auto old_filename = filename + ".old.txt"; +FileBackend::FileBackend(const std::filesystem::path& filename) { + auto old_filename = filename; + old_filename += ".old.txt"; - if (FS::Exists(old_filename)) { - FS::Delete(old_filename); - } - if (FS::Exists(filename)) { - FS::Rename(filename, old_filename); - } + // Existence checks are done within the functions themselves. + // We don't particularly care if these succeed or not. + void(FS::RemoveFile(old_filename)); + void(FS::RenameFile(filename, old_filename)); - // _SH_DENYWR allows read only access to the file for other programs. - // It is #defined to 0 on other platforms - file = FS::IOFile(filename, "w", _SH_DENYWR); + file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); } void FileBackend::Write(const Entry& entry) { @@ -181,7 +178,7 @@ void FileBackend::Write(const Entry& entry) { bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); if (entry.log_level >= Level::Error) { - file.Flush(); + void(file.Flush()); } } 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 @@ #pragma once #include <chrono> +#include <filesystem> #include <memory> #include <string> #include <string_view> -#include "common/file_util.h" +#include "common/fs/file.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -81,7 +82,7 @@ public: */ class FileBackend : public Backend { public: - explicit FileBackend(const std::string& filename); + explicit FileBackend(const std::filesystem::path& filename); static const char* Name() { 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) return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX); } -std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, - std::size_t uncompressed_size) { +std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t uncompressed_size) { std::vector<u8> uncompressed(uncompressed_size); const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()), 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 @@ #pragma once +#include <span> #include <vector> #include "common/common_types.h" @@ -53,7 +54,7 @@ namespace Common::Compression { * * @return the decompressed data. */ -[[nodiscard]] std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, +[[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t uncompressed_size); -} // namespace Common::Compression
\ No newline at end of file +} // 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 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <filesystem> -#include <stdlib.h> +#include <cstdlib> #include <fmt/format.h> -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/nvidia_flags.h" namespace Common { void ConfigureNvidiaEnvironmentFlags() { #ifdef _WIN32 - const std::string shader_path = Common::FS::SanitizePath( - fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir))); - const std::string windows_path = - Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash); - void(Common::FS::CreateFullPath(shader_path + '/')); - void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str())); + const auto nvidia_shader_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "nvidia"; + + if (!Common::FS::CreateDirs(nvidia_shader_dir)) { + return; + } + + const auto windows_path_string = + Common::FS::PathToUTF8String(nvidia_shader_dir.lexically_normal()); + + void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str())); void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); #endif } 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 { } } - return (next - start) * sizeof(MemberType) + Offset; + return static_cast<ptrdiff_t>(static_cast<size_t>(next - start) * sizeof(MemberType) + + Offset); } 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 @@ #include <string_view> #include "common/assert.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" @@ -34,6 +34,10 @@ void LogSettings() { LOG_INFO(Config, "{}: {}", name, value); }; + const auto log_path = [](std::string_view name, const std::filesystem::path& path) { + LOG_INFO(Config, "{}: {}", name, Common::FS::PathToUTF8String(path)); + }; + LOG_INFO(Config, "yuzu Configuration:"); log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); @@ -42,7 +46,7 @@ void LogSettings() { log_setting("System_RegionIndex", values.region_index.GetValue()); log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); - log_setting("CPU_Accuracy", values.cpu_accuracy); + log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue()); log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue()); log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue()); log_setting("Renderer_FrameLimit", values.frame_limit.GetValue()); @@ -59,11 +63,11 @@ void LogSettings() { log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); log_setting("Audio_OutputDevice", values.audio_device_id); log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); - log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)); - log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); - log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)); - log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)); - log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)); + log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); + log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); + log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir)); + log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); + log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); log_setting("Debugging_ProgramArgs", values.program_args); log_setting("Services_BCATBackend", values.bcat_backend); log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local); @@ -106,6 +110,12 @@ void RestoreGlobalState(bool is_powered_on) { // Core values.use_multi_core.SetGlobal(true); + // CPU + values.cpu_accuracy.SetGlobal(true); + values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); + values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); + values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); + // Renderer values.renderer_backend.SetGlobal(true); values.vulkan_device.SetGlobal(true); @@ -130,7 +140,6 @@ void RestoreGlobalState(bool is_powered_on) { values.region_index.SetGlobal(true); values.time_zone_index.SetGlobal(true); values.rng_seed.SetGlobal(true); - values.custom_rtc.SetGlobal(true); values.sound_index.SetGlobal(true); // 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 { Setting<bool> use_multi_core; // Cpu - CPUAccuracy cpu_accuracy; + Setting<CPUAccuracy> cpu_accuracy; bool cpuopt_page_tables; bool cpuopt_block_linking; @@ -126,9 +126,9 @@ struct Values { bool cpuopt_misc_ir; bool cpuopt_reduce_misalign_checks; - bool cpuopt_unsafe_unfuse_fma; - bool cpuopt_unsafe_reduce_fp_error; - bool cpuopt_unsafe_inaccurate_nan; + Setting<bool> cpuopt_unsafe_unfuse_fma; + Setting<bool> cpuopt_unsafe_reduce_fp_error; + Setting<bool> cpuopt_unsafe_inaccurate_nan; // Renderer Setting<RendererBackend> renderer_backend; @@ -157,7 +157,7 @@ struct Values { // System Setting<std::optional<u32>> rng_seed; // Measured in seconds since epoch - Setting<std::optional<std::chrono::seconds>> custom_rtc; + std::optional<std::chrono::seconds> custom_rtc; // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` std::chrono::seconds custom_rtc_differential; 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 @@ #include <locale> #include <sstream> -#include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -93,18 +92,6 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ return true; } -void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, - const std::string& _Filename) { - _CompleteFilename = _Path; - - // check for seperator - if (DIR_SEP_CHR != *_CompleteFilename.rbegin()) - _CompleteFilename += DIR_SEP_CHR; - - // add the filename - _CompleteFilename += _Filename; -} - void SplitString(const std::string& str, const char delim, std::vector<std::string>& output) { std::istringstream iss(str); 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 bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, std::string* _pExtension); -void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, - const std::string& _Filename); [[nodiscard]] std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest); 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 @@ * The maximum height of a red-black tree is 2lg (n+1). */ +#include "common/assert.h" + namespace Common { template <typename T> class RBHead { @@ -325,6 +327,10 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) { while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root() && parent != nullptr) { if (RB_LEFT(parent) == elm) { tmp = RB_RIGHT(parent); + if (!tmp) { + ASSERT_MSG(false, "tmp is invalid!"); + break; + } if (RB_IS_RED(tmp)) { RB_SET_BLACKRED(tmp, parent); RB_ROTATE_LEFT(head, parent, tmp); @@ -366,6 +372,11 @@ void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) { tmp = RB_LEFT(parent); } + if (!tmp) { + ASSERT_MSG(false, "tmp is invalid!"); + break; + } + if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { 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 return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT); } -std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) { +std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) { const std::size_t decompressed_size = ZSTD_getDecompressedSize(compressed.data(), compressed.size()); 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 @@ #pragma once +#include <span> #include <vector> #include "common/common_types.h" @@ -40,6 +41,6 @@ namespace Common::Compression { * * @return the decompressed data. */ -[[nodiscard]] std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed); +[[nodiscard]] std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed); -} // namespace Common::Compression
\ No newline at end of file +} // 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* config.far_code_offset = 256 * 1024 * 1024; // Safe optimizations - if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { if (!Settings::values.cpuopt_page_tables) { config.page_table = nullptr; } @@ -170,15 +170,15 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* } // Unsafe optimizations - if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { config.unsafe_optimizations = true; - if (Settings::values.cpuopt_unsafe_unfuse_fma) { + if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; } - if (Settings::values.cpuopt_unsafe_reduce_fp_error) { + if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } - if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; } } 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* config.far_code_offset = 256 * 1024 * 1024; // Safe optimizations - if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { if (!Settings::values.cpuopt_page_tables) { config.page_table = nullptr; } @@ -210,15 +210,15 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* } // Unsafe optimizations - if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { config.unsafe_optimizations = true; - if (Settings::values.cpuopt_unsafe_unfuse_fma) { + if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; } - if (Settings::values.cpuopt_unsafe_reduce_fp_error) { + if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } - if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; } } 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 @@ #include <memory> #include <utility> -#include "common/file_util.h" +#include "common/fs/fs.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "common/settings.h" @@ -121,7 +121,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, dir->GetName()); } - if (Common::FS::IsDirectory(path)) { + if (Common::FS::IsDir(path)) { return vfs->OpenFile(path + "/main", FileSys::Mode::Read); } @@ -173,7 +173,7 @@ struct System::Impl { const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()); Settings::values.custom_rtc_differential = - Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time; + Settings::values.custom_rtc.value_or(current_time) - current_time; // Create a default fs if one doesn't already exist. if (virtual_filesystem == nullptr) @@ -289,7 +289,8 @@ struct System::Impl { telemetry_session->AddField(performance, "Shutdown_EmulationSpeed", perf_results.emulation_speed * 100.0); - telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps); + telemetry_session->AddField(performance, "Shutdown_Framerate", + perf_results.average_game_fps); telemetry_session->AddField(performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); 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 @@ #include <mbedtls/cmac.h> #include <mbedtls/sha256.h> #include "common/common_funcs.h" -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "common/settings.h" @@ -325,46 +326,55 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) } std::optional<Key128> DeriveSDSeed() { - const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/8000000000000043", - "rb+"); + const auto system_save_43_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000043"; + const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + if (!save_43.IsOpen()) { return std::nullopt; } - const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + - "/Nintendo/Contents/private", - "rb+"); + const auto sd_private_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/private"; + + const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + if (!sd_private.IsOpen()) { return std::nullopt; } std::array<u8, 0x10> private_seed{}; - if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { + if (sd_private.Read(private_seed) != private_seed.size()) { return std::nullopt; } std::array<u8, 0x10> buffer{}; - std::size_t offset = 0; - for (; offset + 0x10 < save_43.GetSize(); ++offset) { - if (!save_43.Seek(offset, SEEK_SET)) { + s64 offset = 0; + for (; offset + 0x10 < static_cast<s64>(save_43.GetSize()); ++offset) { + if (!save_43.Seek(offset)) { + return std::nullopt; + } + + if (save_43.Read(buffer) != buffer.size()) { return std::nullopt; } - save_43.ReadBytes(buffer.data(), buffer.size()); if (buffer == private_seed) { break; } } - if (!save_43.Seek(offset + 0x10, SEEK_SET)) { + if (!save_43.Seek(offset + 0x10)) { return std::nullopt; } Key128 seed{}; - if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { + if (save_43.Read(seed) != seed.size()) { return std::nullopt; } + return seed; } @@ -435,7 +445,7 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { } std::vector<u8> buffer(ticket_save.GetSize()); - if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { + if (ticket_save.Read(buffer) != buffer.size()) { return {}; } @@ -566,27 +576,26 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, KeyManager::KeyManager() { // Initialize keys - const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); - const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); + const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); - if (!Common::FS::Exists(yuzu_keys_dir)) { - Common::FS::CreateDir(yuzu_keys_dir); + if (!Common::FS::CreateDir(yuzu_keys_dir)) { + LOG_ERROR(Core, "Failed to create the keys directory."); } if (Settings::values.use_dev_keys) { dev_mode = true; - AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); + LoadFromFile(yuzu_keys_dir / "dev.keys", false); + LoadFromFile(yuzu_keys_dir / "dev.keys_autogenerated", false); } else { dev_mode = false; - AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); + LoadFromFile(yuzu_keys_dir / "prod.keys", false); + LoadFromFile(yuzu_keys_dir / "prod.keys_autogenerated", false); } - AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); - AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); + LoadFromFile(yuzu_keys_dir / "title.keys", true); + LoadFromFile(yuzu_keys_dir / "title.keys_autogenerated", true); + LoadFromFile(yuzu_keys_dir / "console.keys", false); + LoadFromFile(yuzu_keys_dir / "console.keys_autogenerated", false); } 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_ [](u8 c) { return std::isxdigit(c); }); } -void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { +void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) { + if (!Common::FS::Exists(file_path)) { + return; + } + std::ifstream file; - Common::FS::OpenFStream(file, filename, std::ios_base::in); + Common::FS::OpenFileStream(file, file_path, std::ios_base::in); + if (!file.is_open()) { return; } @@ -694,15 +708,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { } } -void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, - const std::string& filename, bool title) { - if (Common::FS::Exists(dir1 + DIR_SEP + filename)) { - LoadFromFile(dir1 + DIR_SEP + filename, title); - } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) { - LoadFromFile(dir2 + DIR_SEP + filename, title); - } -} - bool KeyManager::BaseDeriveNecessary() const { const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { return !HasKey(key_type, index1, index2); @@ -766,30 +771,35 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { template <size_t Size> void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, const std::array<u8, Size>& key) { - const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); + const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); + std::string filename = "title.keys_autogenerated"; + if (category == KeyCategory::Standard) { filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; } else if (category == KeyCategory::Console) { filename = "console.keys_autogenerated"; } - const auto path = yuzu_keys_dir + DIR_SEP + filename; + const auto path = yuzu_keys_dir / filename; const auto add_info_text = !Common::FS::Exists(path); - Common::FS::CreateFullPath(path); - Common::FS::IOFile file{path, "a"}; + + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append, + Common::FS::FileType::TextFile}; + if (!file.IsOpen()) { return; } + if (add_info_text) { - file.WriteString( + void(file.WriteString( "# This file is autogenerated by Yuzu\n" "# It serves to store keys that were automatically generated from the normal keys\n" - "# If you are experiencing issues involving keys, it may help to delete this file\n"); + "# If you are experiencing issues involving keys, it may help to delete this file\n")); } - file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); + void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key)))); + LoadFromFile(path, category == KeyCategory::Title); } 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) { } bool KeyManager::KeyFileExists(bool title) { - const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath(); - const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir); + const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); + if (title) { - return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || - Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); + return Common::FS::Exists(yuzu_keys_dir / "title.keys"); } if (Settings::values.use_dev_keys) { - return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || - Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); + return Common::FS::Exists(yuzu_keys_dir / "dev.keys"); } - return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || - Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); + return Common::FS::Exists(yuzu_keys_dir / "prod.keys"); } void KeyManager::DeriveSDSeedLazy() { @@ -1115,15 +1122,21 @@ void KeyManager::PopulateTickets() { return; } - const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/80000000000000e1", - "rb+"); - const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/80000000000000e2", - "rb+"); + const auto system_save_e1_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; + + const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + const auto system_save_e2_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; + + const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + const auto blob2 = GetTicketblob(save_e2); + auto res = GetTicketblob(save_e1); - const auto blob2 = GetTicketblob(save2); - auto res = GetTicketblob(save1); const auto idx = res.size(); res.insert(res.end(), blob2.begin(), blob2.end()); 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 @@ #pragma once #include <array> +#include <filesystem> #include <map> #include <optional> #include <string> @@ -283,9 +284,8 @@ private: std::array<u8, 576> eticket_extended_kek{}; bool dev_mode; - void LoadFromFile(const std::string& filename, bool is_title_keys); - void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, - const std::string& filename, bool title); + void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); + template <size_t Size> void WriteKeyToFile(KeyCategory category, std::string_view keyname, 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 @@ // Refer to the license.txt file included. #include <fmt/format.h> -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "core/file_sys/bis_factory.h" #include "core/file_sys/mode.h" #include "core/file_sys/registered_cache.h" @@ -85,7 +85,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const { auto& keys = Core::Crypto::KeyManager::Instance(); Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory( - Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)}; + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir), Mode::Read)}; keys.PopulateFromPartitionData(pdm); 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 @@ namespace FileSys { enum class Mode : u32 { - Read = 1, - Write = 2, + Read = 1 << 0, + Write = 1 << 1, ReadWrite = Read | Write, - Append = 4, + Append = 1 << 2, + ReadAppend = Read | Append, WriteAppend = Write | Append, + All = ReadWrite | Append, }; 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 @@ #include <iterator> #include <utility> -#include "common/file_util.h" #include "common/logging/log.h" #include "core/file_sys/partition_filesystem.h" #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 @@ #include <cstddef> #include <cstring> -#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #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 @@ #include <regex> #include <mbedtls/sha256.h> #include "common/assert.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #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 @@ #include <algorithm> #include <numeric> #include <string> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/logging/backend.h" #include "core/file_sys/mode.h" #include "core/file_sys/vfs.h" @@ -122,15 +121,14 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_ return nullptr; for (const auto& file : old_dir->GetFiles()) { - const auto x = - CopyFile(old_path + DIR_SEP + file->GetName(), new_path + DIR_SEP + file->GetName()); + const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName()); if (x == nullptr) return nullptr; } for (const auto& dir : old_dir->GetSubdirectories()) { const auto x = - CopyDirectory(old_path + DIR_SEP + dir->GetName(), new_path + DIR_SEP + dir->GetName()); + CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName()); if (x == nullptr) return nullptr; } 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 @@ #pragma GCC diagnostic pop #endif +#include "common/fs/path_util.h" #include "common/logging/backend.h" #include "core/file_sys/vfs.h" #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 @@ #include <iterator> #include <utility> #include "common/assert.h" -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "core/file_sys/vfs_real.h" @@ -16,33 +17,31 @@ namespace FileSys { namespace FS = Common::FS; -static std::string ModeFlagsToString(Mode mode) { - std::string mode_str; - - // Calculate the correct open mode for the file. - if (True(mode & Mode::Read) && True(mode & Mode::Write)) { - if (True(mode & Mode::Append)) { - mode_str = "a+"; - } else { - mode_str = "r+"; - } - } else { - if (True(mode & Mode::Read)) { - mode_str = "r"; - } else if (True(mode & Mode::Append)) { - mode_str = "a"; - } else if (True(mode & Mode::Write)) { - mode_str = "w"; - } else { - UNREACHABLE_MSG("Invalid file open mode: {:02X}", static_cast<u8>(mode)); - } +namespace { + +constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { + switch (mode) { + case Mode::Read: + return FS::FileAccessMode::Read; + case Mode::Write: + return FS::FileAccessMode::Write; + case Mode::ReadWrite: + return FS::FileAccessMode::ReadWrite; + case Mode::Append: + return FS::FileAccessMode::Append; + case Mode::ReadAppend: + return FS::FileAccessMode::ReadAppend; + case Mode::WriteAppend: + return FS::FileAccessMode::Append; + case Mode::All: + return FS::FileAccessMode::ReadAppend; + default: + return {}; } - - mode_str += "b"; - - return mode_str; } +} // Anonymous namespace + RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} RealVfsFilesystem::~RealVfsFilesystem() = default; @@ -63,7 +62,7 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { if (!FS::Exists(path)) { return VfsEntryType::None; } - if (FS::IsDirectory(path)) { + if (FS::IsDir(path)) { return VfsEntryType::Directory; } @@ -81,12 +80,13 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { } } - if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { - FS::CreateEmptyFile(path); + auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); + + if (!backing) { + return nullptr; } - auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str()); - cache.insert_or_assign(path, backing); + cache.insert_or_assign(path, std::move(backing)); // Cannot use make_shared as RealVfsFile constructor is private return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); @@ -94,25 +94,29 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); - if (!FS::Exists(path)) { - FS::CreateFullPath(path_fwd); - if (!FS::CreateEmptyFile(path)) { + // Current usages of CreateFile expect to delete the contents of an existing file. + if (FS::IsFile(path)) { + FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; + + if (!temp.IsOpen()) { return nullptr; } + + temp.Close(); + + return OpenFile(path, perms); + } + + if (!FS::NewFile(path)) { + return nullptr; } + return OpenFile(path, perms); } VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); - const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); - - if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || - !FS::Copy(old_path, new_path)) { - return nullptr; - } - return OpenFile(new_path, Mode::ReadWrite); + // Unused + return nullptr; } 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_ file->Close(); } - if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || - !FS::Rename(old_path, new_path)) { + if (!FS::RenameFile(old_path, new_path)) { return nullptr; } cache.erase(old_path); - if (file->Open(new_path, "r+b")) { + file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); + if (file->IsOpen()) { cache.insert_or_assign(new_path, std::move(file)); } else { 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_) { cache.erase(path); } - return FS::Delete(path); + return FS::RemoveFile(path); } VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { @@ -168,12 +172,8 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash); - if (!FS::Exists(path)) { - FS::CreateFullPath(path_fwd); - if (!FS::CreateDir(path)) { - return nullptr; - } + if (!FS::CreateDirs(path)) { + return nullptr; } // Cannot use make_shared as RealVfsDirectory constructor is private return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); @@ -181,13 +181,8 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { - const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); - const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); - if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) { - return nullptr; - } - FS::CopyDir(old_path, new_path); - return OpenDirectory(new_path, Mode::ReadWrite); + // Unused + return nullptr; } VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, @@ -195,8 +190,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); - if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) || - !FS::Rename(old_path, new_path)) { + if (!FS::RenameDir(old_path, new_path)) { return nullptr; } @@ -208,7 +202,7 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, const auto file_old_path = FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); - auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), + auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), FS::DirectorySeparator::PlatformDefault); const auto& cached = cache[file_old_path]; @@ -218,7 +212,8 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, auto file = cached.lock(); cache.erase(file_old_path); - if (file->Open(file_new_path, "r+b")) { + file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); + if (file->IsOpen()) { cache.insert_or_assign(std::move(file_new_path), std::move(file)); } else { 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_) { cache.erase(kv.first); } - return FS::DeleteDirRecursively(path); + return FS::RemoveDirRecursively(path); } RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, const std::string& path_, Mode perms_) : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), - path_components(FS::SplitPathComponents(path_)), - parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), - perms(perms_) {} + path_components(FS::SplitPathComponents(path_)), perms(perms_) {} RealVfsFile::~RealVfsFile() = default; @@ -266,7 +259,7 @@ std::size_t RealVfsFile::GetSize() const { } bool RealVfsFile::Resize(std::size_t new_size) { - return backing->Resize(new_size); + return backing->SetSize(new_size); } VirtualDir RealVfsFile::GetContainingDirectory() const { @@ -274,33 +267,33 @@ VirtualDir RealVfsFile::GetContainingDirectory() const { } bool RealVfsFile::IsWritable() const { - return True(perms & Mode::WriteAppend); + return True(perms & Mode::Write); } bool RealVfsFile::IsReadable() const { - return True(perms & Mode::ReadWrite); + return True(perms & Mode::Read); } std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { - if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { + if (!backing->Seek(static_cast<s64>(offset))) { return 0; } - return backing->ReadBytes(data, length); + return backing->ReadSpan(std::span{data, length}); } std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { - if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) { + if (!backing->Seek(static_cast<s64>(offset))) { return 0; } - return backing->WriteBytes(data, length); + return backing->WriteSpan(std::span{data, length}); } bool RealVfsFile::Rename(std::string_view name) { - return base.MoveFile(path, parent_path + DIR_SEP + std::string(name)) != nullptr; + return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; } -bool RealVfsFile::Close() { - return backing->Close(); +void RealVfsFile::Close() { + backing->Close(); } // 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>( } std::vector<VirtualFile> out; - FS::ForeachDirectoryEntry( - nullptr, path, - [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { - const std::string full_path = directory + DIR_SEP + filename; - if (!FS::IsDirectory(full_path)) { - out.emplace_back(base.OpenFile(full_path, perms)); - } - return true; - }); + + const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { + const auto full_path_string = FS::PathToUTF8String(full_path); + + out.emplace_back(base.OpenFile(full_path_string, perms)); + + return true; + }; + + FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File); return out; } @@ -333,42 +327,41 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi } std::vector<VirtualDir> out; - FS::ForeachDirectoryEntry( - nullptr, path, - [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { - const std::string full_path = directory + DIR_SEP + filename; - if (FS::IsDirectory(full_path)) { - out.emplace_back(base.OpenDirectory(full_path, perms)); - } - return true; - }); + + const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { + const auto full_path_string = FS::PathToUTF8String(full_path); + + out.emplace_back(base.OpenDirectory(full_path_string, perms)); + + return true; + }; + + FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory); return out; } RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), - path_components(FS::SplitPathComponents(path)), - parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)), - perms(perms_) { - if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) { - FS::CreateDir(path); + path_components(FS::SplitPathComponents(path)), perms(perms_) { + if (!FS::Exists(path) && True(perms & Mode::Write)) { + void(FS::CreateDirs(path)); } } RealVfsDirectory::~RealVfsDirectory() = default; VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { - const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); - if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::Exists(full_path) || FS::IsDir(full_path)) { return nullptr; } return base.OpenFile(full_path, perms); } VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { - const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); - if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::Exists(full_path) || !FS::IsDir(full_path)) { return nullptr; } return base.OpenDirectory(full_path, perms); @@ -383,17 +376,20 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { } VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { - const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); + if (!FS::CreateParentDirs(full_path)) { + return nullptr; + } return base.CreateFile(full_path, perms); } VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) { - const auto full_path = FS::SanitizePath(path + DIR_SEP + std::string(relative_path)); + const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); return base.CreateDirectory(full_path, perms); } bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { - const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name)); + const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name)); return base.DeleteDirectory(full_path); } @@ -406,11 +402,11 @@ std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const { } bool RealVfsDirectory::IsWritable() const { - return True(perms & Mode::WriteAppend); + return True(perms & Mode::Write); } bool RealVfsDirectory::IsReadable() const { - return True(perms & Mode::ReadWrite); + return True(perms & Mode::Read); } std::string RealVfsDirectory::GetName() const { @@ -426,27 +422,27 @@ VirtualDir RealVfsDirectory::GetParentDirectory() const { } VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { - const std::string subdir_path = (path + DIR_SEP).append(name); + const std::string subdir_path = (path + '/').append(name); return base.CreateDirectory(subdir_path, perms); } VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { - const std::string file_path = (path + DIR_SEP).append(name); + const std::string file_path = (path + '/').append(name); return base.CreateFile(file_path, perms); } bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { - const std::string subdir_path = (path + DIR_SEP).append(name); + const std::string subdir_path = (path + '/').append(name); return base.DeleteDirectory(subdir_path); } bool RealVfsDirectory::DeleteFile(std::string_view name) { - const std::string file_path = (path + DIR_SEP).append(name); + const std::string file_path = (path + '/').append(name); return base.DeleteFile(file_path); } bool RealVfsDirectory::Rename(std::string_view name) { - const std::string new_name = (parent_path + DIR_SEP).append(name); + const std::string new_name = (parent_path + '/').append(name); return base.MoveFile(path, new_name) != nullptr; } @@ -462,14 +458,17 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() } std::map<std::string, VfsEntryType, std::less<>> out; - FS::ForeachDirectoryEntry( - nullptr, path, - [&out](u64* entries_out, const std::string& directory, const std::string& filename) { - const std::string full_path = directory + DIR_SEP + filename; - out.emplace(filename, - FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File); - return true; - }); + + const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) { + const auto filename = FS::PathToUTF8String(full_path.filename()); + + out.insert_or_assign(filename, + FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File); + + return true; + }; + + FS::IterateDirEntries(path, callback); return out; } 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: RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, const std::string& path, Mode perms = Mode::Read); - bool Close(); + void Close(); RealVfsFilesystem& base; std::shared_ptr<Common::FS::IOFile> backing; std::string path; std::string parent_path; std::vector<std::string> path_components; - std::vector<std::string> parent_components; Mode perms; }; @@ -110,7 +109,6 @@ private: std::string path; std::string parent_path; std::vector<std::string> path_components; - std::vector<std::string> parent_components; Mode perms; }; 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 @@ #include <mbedtls/md.h> #include <mbedtls/sha256.h> -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/string_util.h" #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: memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); - ctx.ClearIncomingObjects(); - IPC::CommandHeader header{}; // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory // padding. - u32 raw_data_size = ctx.IsTipc() - ? normal_params_size - 1 - : sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; - + u32 raw_data_size = ctx.write_size = + ctx.IsTipc() ? normal_params_size - 1 : normal_params_size; u32 num_handles_to_move{}; u32 num_domain_objects{}; const bool always_move_handles{ @@ -101,16 +97,20 @@ public: } if (ctx.Session()->IsDomain()) { - raw_data_size += static_cast<u32>(sizeof(DomainMessageHeader) / 4 + num_domain_objects); + raw_data_size += + static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects); + ctx.write_size += num_domain_objects; } if (ctx.IsTipc()) { header.type.Assign(ctx.GetCommandType()); + } else { + raw_data_size += static_cast<u32>(sizeof(IPC::DataPayloadHeader) / sizeof(u32) + 4 + + normal_params_size); } - ctx.data_size = static_cast<u32>(raw_data_size); - header.data_size.Assign(static_cast<u32>(raw_data_size)); - if (num_handles_to_copy != 0 || num_handles_to_move != 0) { + header.data_size.Assign(raw_data_size); + if (num_handles_to_copy || num_handles_to_move) { header.enable_handle_descriptor.Assign(1); } PushRaw(header); @@ -143,7 +143,8 @@ public: data_payload_index = index; ctx.data_payload_offset = index; - ctx.domain_offset = index + raw_data_size / 4; + ctx.write_size += index; + ctx.domain_offset = static_cast<u32>(index + raw_data_size / sizeof(u32)); } template <class T> @@ -151,8 +152,8 @@ public: if (context->Session()->IsDomain()) { context->AddDomainObject(std::move(iface)); } else { - // kernel.CurrentProcess()->GetResourceLimit()->Reserve( - // Kernel::LimitableResource::Sessions, 1); + kernel.CurrentProcess()->GetResourceLimit()->Reserve( + Kernel::LimitableResource::Sessions, 1); auto* session = Kernel::KSession::Create(kernel); session->Initialize(nullptr, iface->GetServiceName()); @@ -167,24 +168,6 @@ public: PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(args)...)); } - void ValidateHeader() { - const std::size_t num_domain_objects = context->NumDomainObjects(); - const std::size_t num_move_objects = context->NumMoveObjects(); - ASSERT_MSG(!num_domain_objects || !num_move_objects, - "cannot move normal handles and domain objects"); - ASSERT_MSG((index - data_payload_index) == normal_params_size, - "normal_params_size value is incorrect"); - ASSERT_MSG((num_domain_objects + num_move_objects) == num_objects_to_move, - "num_objects_to_move value is incorrect"); - ASSERT_MSG(context->NumCopyObjects() == num_handles_to_copy, - "num_handles_to_copy value is incorrect"); - } - - // Validate on destruction, as there shouldn't be any case where we don't want it - ~ResponseBuilder() { - ValidateHeader(); - } - void PushImpl(s8 value); void PushImpl(s16 value); void PushImpl(s32 value); @@ -404,7 +387,7 @@ public: std::shared_ptr<T> PopIpcInterface() { ASSERT(context->Session()->IsDomain()); ASSERT(context->GetDomainMessageHeader().input_object_count > 0); - return context->GetDomainRequestHandler<T>(Pop<u32>() - 1); + return context->GetDomainHandler<T>(Pop<u32>() - 1); } }; 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; SessionRequestHandler::~SessionRequestHandler() = default; void SessionRequestHandler::ClientConnected(KServerSession* session) { - session->SetHleHandler(shared_from_this()); + session->SetSessionHandler(shared_from_this()); } void SessionRequestHandler::ClientDisconnected(KServerSession* session) { - session->SetHleHandler(nullptr); + session->SetSessionHandler(nullptr); } HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, @@ -64,19 +64,15 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 if (command_header->enable_handle_descriptor) { handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>(); if (handle_descriptor_header->send_current_pid) { - rp.Skip(2, false); + pid = rp.Pop<u64>(); } if (incoming) { // Populate the object lists with the data in the IPC request. for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { - const u32 copy_handle{rp.Pop<Handle>()}; - copy_handles.push_back(copy_handle); - copy_objects.push_back(handle_table.GetObject(copy_handle).GetPointerUnsafe()); + incoming_copy_handles.push_back(rp.Pop<Handle>()); } for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) { - const u32 move_handle{rp.Pop<Handle>()}; - move_handles.push_back(move_handle); - move_objects.push_back(handle_table.GetObject(move_handle).GetPointerUnsafe()); + incoming_move_handles.push_back(rp.Pop<Handle>()); } } else { // 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 } } - for (unsigned i = 0; i < command_header->num_buf_x_descriptors; ++i) { + for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>()); } - for (unsigned i = 0; i < command_header->num_buf_a_descriptors; ++i) { + for (u32 i = 0; i < command_header->num_buf_a_descriptors; ++i) { buffer_a_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); } - for (unsigned i = 0; i < command_header->num_buf_b_descriptors; ++i) { + for (u32 i = 0; i < command_header->num_buf_b_descriptors; ++i) { buffer_b_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); } - for (unsigned i = 0; i < command_header->num_buf_w_descriptors; ++i) { + for (u32 i = 0; i < command_header->num_buf_w_descriptors; ++i) { buffer_w_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>()); } @@ -148,14 +144,14 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) { buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); } else { - unsigned num_buf_c_descriptors = - static_cast<unsigned>(command_header->buf_c_descriptor_flags.Value()) - 2; + u32 num_buf_c_descriptors = + static_cast<u32>(command_header->buf_c_descriptor_flags.Value()) - 2; // This is used to detect possible underflows, in case something is broken // with the two ifs above and the flags value is == 0 || == 1. ASSERT(num_buf_c_descriptors < 14); - for (unsigned i = 0; i < num_buf_c_descriptors; ++i) { + for (u32 i = 0; i < num_buf_c_descriptors; ++i) { buffer_c_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>()); } } @@ -186,26 +182,14 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t auto& owner_process = *requesting_thread.GetOwnerProcess(); auto& handle_table = owner_process.GetHandleTable(); - // The data_size already includes the payload header, the padding and the domain header. - std::size_t size{}; - - if (IsTipc()) { - size = cmd_buf.size(); - } else { - size = data_payload_offset + data_size - sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; - if (Session()->IsDomain()) { - size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); - } - } - - for (auto& object : copy_objects) { + for (auto& object : outgoing_copy_objects) { Handle handle{}; if (object) { R_TRY(handle_table.Add(&handle, object)); } cmd_buf[current_offset++] = handle; } - for (auto& object : move_objects) { + for (auto& object : outgoing_move_objects) { Handle handle{}; if (object) { R_TRY(handle_table.Add(&handle, object)); @@ -220,9 +204,9 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t // TODO(Subv): This completely ignores C buffers. if (Session()->IsDomain()) { - current_offset = domain_offset - static_cast<u32>(domain_objects.size()); - for (const auto& object : domain_objects) { - server_session->AppendDomainRequestHandler(object); + current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size()); + for (const auto& object : outgoing_domain_objects) { + server_session->AppendDomainHandler(object); cmd_buf[current_offset++] = static_cast<u32_le>(server_session->NumDomainRequestHandlers()); } @@ -230,7 +214,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_t // Copy the translated command buffer back into the thread's command buffer area. memory.WriteBlock(owner_process, requesting_thread.GetTLSAddress(), cmd_buf.data(), - size * sizeof(u32)); + write_size * sizeof(u32)); return RESULT_SUCCESS; } 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 @@ #include <string> #include <type_traits> #include <vector> -#include <boost/container/small_vector.hpp> + +#include "common/assert.h" #include "common/common_types.h" #include "common/concepts.h" #include "common/swap.h" @@ -84,6 +85,69 @@ public: void ClientDisconnected(KServerSession* session); }; +using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; + +/** + * Manages the underlying HLE requests for a session, and whether (or not) the session should be + * treated as a domain. This is managed separately from server sessions, as this state is shared + * when objects are cloned. + */ +class SessionRequestManager final { +public: + SessionRequestManager() = default; + + bool IsDomain() const { + return is_domain; + } + + void ConvertToDomain() { + domain_handlers = {session_handler}; + is_domain = true; + } + + std::size_t DomainHandlerCount() const { + return domain_handlers.size(); + } + + bool HasSessionHandler() const { + return session_handler != nullptr; + } + + SessionRequestHandler& SessionHandler() { + return *session_handler; + } + + const SessionRequestHandler& SessionHandler() const { + return *session_handler; + } + + void CloseDomainHandler(std::size_t index) { + if (index < DomainHandlerCount()) { + domain_handlers[index] = nullptr; + } else { + UNREACHABLE_MSG("Unexpected handler index {}", index); + } + } + + SessionRequestHandlerPtr DomainHandler(std::size_t index) const { + ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index); + return domain_handlers.at(index); + } + + void AppendDomainHandler(SessionRequestHandlerPtr&& handler) { + domain_handlers.emplace_back(std::move(handler)); + } + + void SetSessionHandler(SessionRequestHandlerPtr&& handler) { + session_handler = std::move(handler); + } + +private: + bool is_domain{}; + SessionRequestHandlerPtr session_handler; + std::vector<SessionRequestHandlerPtr> domain_handlers; +}; + /** * Class containing information about an in-flight IPC request being handled by an HLE service * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and @@ -150,6 +214,10 @@ public: return command_header->type; } + u64 GetPID() const { + return pid; + } + u32 GetDataPayloadOffset() const { return data_payload_offset; } @@ -220,53 +288,32 @@ public: bool CanWriteBuffer(std::size_t buffer_index = 0) const; Handle GetCopyHandle(std::size_t index) const { - return copy_handles.at(index); + return incoming_copy_handles.at(index); } Handle GetMoveHandle(std::size_t index) const { - return move_handles.at(index); + return incoming_move_handles.at(index); } void AddMoveObject(KAutoObject* object) { - move_objects.emplace_back(object); + outgoing_move_objects.emplace_back(object); } void AddCopyObject(KAutoObject* object) { - copy_objects.emplace_back(object); + outgoing_copy_objects.emplace_back(object); } - void AddDomainObject(std::shared_ptr<SessionRequestHandler> object) { - domain_objects.emplace_back(std::move(object)); + void AddDomainObject(SessionRequestHandlerPtr object) { + outgoing_domain_objects.emplace_back(std::move(object)); } template <typename T> - std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const { - return std::static_pointer_cast<T>(domain_request_handlers.at(index)); + std::shared_ptr<T> GetDomainHandler(std::size_t index) const { + return std::static_pointer_cast<T>(manager->DomainHandler(index)); } - void SetDomainRequestHandlers( - const std::vector<std::shared_ptr<SessionRequestHandler>>& handlers) { - domain_request_handlers = handlers; - } - - /// Clears the list of objects so that no lingering objects are written accidentally to the - /// response buffer. - void ClearIncomingObjects() { - move_objects.clear(); - copy_objects.clear(); - domain_objects.clear(); - } - - std::size_t NumMoveObjects() const { - return move_objects.size(); - } - - std::size_t NumCopyObjects() const { - return copy_objects.size(); - } - - std::size_t NumDomainObjects() const { - return domain_objects.size(); + void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) { + manager = std::move(manager_); } std::string Description() const; @@ -288,12 +335,12 @@ private: Kernel::KServerSession* server_session{}; KThread* thread; - // TODO(yuriks): Check common usage of this and optimize size accordingly - boost::container::small_vector<Handle, 8> move_handles; - boost::container::small_vector<Handle, 8> copy_handles; - boost::container::small_vector<KAutoObject*, 8> move_objects; - boost::container::small_vector<KAutoObject*, 8> copy_objects; - boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects; + std::vector<Handle> incoming_move_handles; + std::vector<Handle> incoming_copy_handles; + + std::vector<KAutoObject*> outgoing_move_objects; + std::vector<KAutoObject*> outgoing_copy_objects; + std::vector<SessionRequestHandlerPtr> outgoing_domain_objects; std::optional<IPC::CommandHeader> command_header; std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header; @@ -305,13 +352,14 @@ private: std::vector<IPC::BufferDescriptorABW> buffer_w_desciptors; std::vector<IPC::BufferDescriptorC> buffer_c_desciptors; + u32_le command{}; + u64 pid{}; + u32 write_size{}; u32 data_payload_offset{}; u32 handles_offset{}; u32 domain_offset{}; - u32 data_size{}; - u32_le command{}; - std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; + std::shared_ptr<SessionRequestManager> manager; bool is_thread_waiting{}; 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; template <typename T> VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAddr address, size_t num_objects) { + // TODO(bunnei): This is just a place holder. We should initialize the appropriate KSlabHeap for + // kernel object type T with the backing kernel memory pointer once we emulate kernel memory. + const size_t size = Common::AlignUp(sizeof(T) * num_objects, alignof(void*)); VAddr start = Common::AlignUp(address, alignof(T)); + // This is intentionally empty. Once KSlabHeap is fully implemented, we can replace this with + // the pointer to emulated memory to pass along. Until then, KSlabHeap will just allocate/free + // host memory. + void* backing_kernel_memory{}; + if (size > 0) { const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1); ASSERT(region != nullptr); ASSERT(region->IsDerivedFrom(KMemoryRegionType_KernelSlab)); - T::InitializeSlabHeap(system.Kernel(), system.Memory().GetKernelBuffer(start, size), size); + T::InitializeSlabHeap(system.Kernel(), backing_kernel_memory, size); } 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 { ResultCode KClientPort::CreateSession(KClientSession** out) { // Reserve a new session from the resource limit. - // KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), - // LimitableResource::Sessions); - // R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); + KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), + LimitableResource::Sessions); + R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); // Update the session counts. { @@ -104,7 +104,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out) { session->Initialize(this, parent->GetName()); // Commit the session reservation. - // session_reservation.Commit(); + session_reservation.Commit(); // Register the session. 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: const KPort* GetParent() const { return parent; } + KPort* GetParent() { + return parent; + } s32 GetNumSessions() const { 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) { R_UNLESS(state == State::Normal, ResultPortClosed); - if (server.HasHLEHandler()) { - server.GetHLEHandler()->ClientConnected(session); - } else { - server.EnqueueSession(session); - } + server.EnqueueSession(session); + server.GetSessionRequestHandler()->ClientConnected(server.AcceptSession()); return RESULT_SUCCESS; } 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: explicit KServerPort(KernelCore& kernel_); virtual ~KServerPort() override; - using HLEHandler = std::shared_ptr<SessionRequestHandler>; - void Initialize(KPort* parent_, std::string&& name_); /// Whether or not this server port has an HLE handler available. - bool HasHLEHandler() const { - return hle_handler != nullptr; + bool HasSessionRequestHandler() const { + return session_handler != nullptr; } /// Gets the HLE handler for this port. - HLEHandler GetHLEHandler() const { - return hle_handler; + SessionRequestHandlerPtr GetSessionRequestHandler() const { + return session_handler; } /** * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port * will inherit a reference to this handler. */ - void SetHleHandler(HLEHandler hle_handler_) { - hle_handler = std::move(hle_handler_); + void SetSessionHandler(SessionRequestHandlerPtr&& handler) { + session_handler = std::move(handler); } void EnqueueSession(KServerSession* pending_session); @@ -73,7 +71,7 @@ private: private: SessionList session_list; - HLEHandler hle_handler; + SessionRequestHandlerPtr session_handler; KPort* parent{}; }; 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 @@ namespace Kernel { -KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} +KServerSession::KServerSession(KernelCore& kernel_) + : KSynchronizationObject{kernel_}, manager{std::make_shared<SessionRequestManager>()} {} KServerSession::~KServerSession() { kernel.ReleaseServiceThread(service_thread); @@ -43,14 +44,8 @@ void KServerSession::Destroy() { } void KServerSession::OnClientClosed() { - // We keep a shared pointer to the hle handler to keep it alive throughout - // the call to ClientDisconnected, as ClientDisconnected invalidates the - // hle_handler member itself during the course of the function executing. - std::shared_ptr<SessionRequestHandler> handler = hle_handler; - if (handler) { - // Note that after this returns, this server session's hle_handler is - // invalidated (set to null). - handler->ClientDisconnected(this); + if (manager->HasSessionHandler()) { + manager->SessionHandler().ClientDisconnected(this); } } @@ -66,12 +61,12 @@ bool KServerSession::IsSignaled() const { return false; } -void KServerSession::AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler) { - domain_request_handlers.push_back(std::move(handler)); +void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) { + manager->AppendDomainHandler(std::move(handler)); } std::size_t KServerSession::NumDomainRequestHandlers() const { - return domain_request_handlers.size(); + return manager->DomainHandlerCount(); } ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { @@ -80,14 +75,14 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co } // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs - context.SetDomainRequestHandlers(domain_request_handlers); + context.SetSessionRequestManager(manager); // If there is a DomainMessageHeader, then this is CommandType "Request" const auto& domain_message_header = context.GetDomainMessageHeader(); const u32 object_id{domain_message_header.object_id}; switch (domain_message_header.command) { case IPC::DomainMessageHeader::CommandType::SendMessage: - if (object_id > domain_request_handlers.size()) { + if (object_id > manager->DomainHandlerCount()) { LOG_CRITICAL(IPC, "object_id {} is too big! This probably means a recent service call " "to {} needed to return a new interface!", @@ -95,12 +90,12 @@ ResultCode KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& co UNREACHABLE(); return RESULT_SUCCESS; // Ignore error if asserts are off } - return domain_request_handlers[object_id - 1]->HandleSyncRequest(*this, context); + return manager->DomainHandler(object_id - 1)->HandleSyncRequest(*this, context); case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); - domain_request_handlers[object_id - 1] = nullptr; + manager->CloseDomainHandler(object_id - 1); IPC::ResponseBuilder rb{context, 2}; rb.Push(RESULT_SUCCESS); @@ -133,14 +128,14 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) { if (IsDomain() && context.HasDomainMessageHeader()) { result = HandleDomainSyncRequest(context); // If there is no domain header, the regular session handler is used - } else if (hle_handler != nullptr) { + } else if (manager->HasSessionHandler()) { // If this ServerSession has an associated HLE handler, forward the request to it. - result = hle_handler->HandleSyncRequest(*this, context); + result = manager->SessionHandler().HandleSyncRequest(*this, context); } if (convert_to_domain) { - ASSERT_MSG(IsSession(), "ServerSession is already a domain instance."); - domain_request_handlers = {hle_handler}; + ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance."); + manager->ConvertToDomain(); convert_to_domain = false; } 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 @@ #include <boost/intrusive/list.hpp> #include "common/threadsafe_queue.h" +#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/service_thread.h" #include "core/hle/result.h" @@ -64,8 +65,8 @@ public: * instead of the regular IPC machinery. (The regular IPC machinery is currently not * implemented.) */ - void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) { - hle_handler = std::move(hle_handler_); + void SetSessionHandler(SessionRequestHandlerPtr handler) { + manager->SetSessionHandler(std::move(handler)); } /** @@ -82,7 +83,7 @@ public: /// Adds a new domain request handler to the collection of request handlers within /// this ServerSession instance. - void AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler); + void AppendDomainHandler(SessionRequestHandlerPtr handler); /// Retrieves the total number of domain request handlers that have been /// appended to this ServerSession instance. @@ -90,12 +91,7 @@ public: /// Returns true if the session has been converted to a domain, otherwise False bool IsDomain() const { - return !IsSession(); - } - - /// Returns true if this session has not been converted to a domain, otherwise false. - bool IsSession() const { - return domain_request_handlers.empty(); + return manager->IsDomain(); } /// Converts the session to a domain at the end of the current command @@ -103,6 +99,21 @@ public: convert_to_domain = true; } + /// Gets the session request manager, which forwards requests to the underlying service + std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() { + return manager; + } + + /// Gets the session request manager, which forwards requests to the underlying service + const std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() const { + return manager; + } + + /// Sets the session request manager, which forwards requests to the underlying service + void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) { + manager = std::move(manager_); + } + private: /// Queues a sync request from the emulated application. ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory); @@ -114,11 +125,8 @@ private: /// object handle. ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context); - /// This session's HLE request handler (applicable when not a domain) - std::shared_ptr<SessionRequestHandler> hle_handler; - - /// This is the list of domain request handlers (after conversion to a domain) - std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers; + /// This session's HLE request handlers + std::shared_ptr<SessionRequestManager> manager; /// When set to True, converts the session to a domain at the end of the command 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() { void KSession::PostDestroy(uintptr_t arg) { // Release the session count resource the owner process holds. KProcess* owner = reinterpret_cast<KProcess*>(arg); - // owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1); + owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1); owner->Close(); } 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: return port; } + KClientPort* GetParent() { + return port; + } + private: enum class State : u8 { 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 @@ #pragma once -#include <atomic> - -#include "common/assert.h" -#include "common/common_types.h" - namespace Kernel { -namespace impl { - -class KSlabHeapImpl final : NonCopyable { -public: - struct Node { - Node* next{}; - }; - - constexpr KSlabHeapImpl() = default; - - void Initialize(std::size_t size) { - ASSERT(head == nullptr); - obj_size = size; - } - - constexpr std::size_t GetObjectSize() const { - return obj_size; - } - - Node* GetHead() const { - return head; - } - - void* Allocate() { - Node* ret = head.load(); - - do { - if (ret == nullptr) { - break; - } - } while (!head.compare_exchange_weak(ret, ret->next)); - - return ret; - } - - void Free(void* obj) { - Node* node = static_cast<Node*>(obj); - - Node* cur_head = head.load(); - do { - node->next = cur_head; - } while (!head.compare_exchange_weak(cur_head, node)); - } - -private: - std::atomic<Node*> head{}; - std::size_t obj_size{}; -}; - -} // namespace impl - -class KSlabHeapBase : NonCopyable { -public: - constexpr KSlabHeapBase() = default; - - constexpr bool Contains(uintptr_t addr) const { - return start <= addr && addr < end; - } - - constexpr std::size_t GetSlabHeapSize() const { - return (end - start) / GetObjectSize(); - } - - constexpr std::size_t GetObjectSize() const { - return impl.GetObjectSize(); - } +class KernelCore; - constexpr uintptr_t GetSlabHeapAddress() const { - return start; - } - - std::size_t GetObjectIndexImpl(const void* obj) const { - return (reinterpret_cast<uintptr_t>(obj) - start) / GetObjectSize(); - } - - std::size_t GetPeakIndex() const { - return GetObjectIndexImpl(reinterpret_cast<const void*>(peak)); - } - - void* AllocateImpl() { - return impl.Allocate(); - } - - void FreeImpl(void* obj) { - // Don't allow freeing an object that wasn't allocated from this heap - ASSERT(Contains(reinterpret_cast<uintptr_t>(obj))); - - impl.Free(obj); - } - - void InitializeImpl(std::size_t obj_size, void* memory, std::size_t memory_size) { - // Ensure we don't initialize a slab using null memory - ASSERT(memory != nullptr); - - // Initialize the base allocator - impl.Initialize(obj_size); - - // Set our tracking variables - const std::size_t num_obj = (memory_size / obj_size); - start = reinterpret_cast<uintptr_t>(memory); - end = start + num_obj * obj_size; - peak = start; - - // Free the objects - u8* cur = reinterpret_cast<u8*>(end); - - for (std::size_t i{}; i < num_obj; i++) { - cur -= obj_size; - impl.Free(cur); - } - } - -private: - using Impl = impl::KSlabHeapImpl; - - Impl impl; - uintptr_t peak{}; - uintptr_t start{}; - uintptr_t end{}; -}; +/// This is a placeholder class to manage slab heaps for kernel objects. For now, we just allocate +/// these with new/delete, but this can be re-implemented later to allocate these in emulated +/// memory. template <typename T> -class KSlabHeap final : public KSlabHeapBase { +class KSlabHeap final : NonCopyable { public: - constexpr KSlabHeap() : KSlabHeapBase() {} + KSlabHeap() = default; - void Initialize(void* memory, std::size_t memory_size) { - InitializeImpl(sizeof(T), memory, memory_size); + void Initialize([[maybe_unused]] void* memory, [[maybe_unused]] std::size_t memory_size) { + // Placeholder that should initialize the backing slab heap implementation. } T* Allocate() { - T* obj = static_cast<T*>(AllocateImpl()); - if (obj != nullptr) { - new (obj) T(); - } - return obj; + return new T(); } T* AllocateWithKernel(KernelCore& kernel) { - T* obj = static_cast<T*>(AllocateImpl()); - if (obj != nullptr) { - new (obj) T(kernel); - } - return obj; + return new T(kernel); } void Free(T* obj) { - FreeImpl(obj); - } - - constexpr std::size_t GetObjectIndex(const T* obj) const { - return GetObjectIndexImpl(obj); + delete obj; } }; 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: } size_t GetSize() const { - return is_initialized ? size * PageSize : 0; + return is_initialized ? size : 0; } 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 { CapabilityOffset_Syscall = 4, CapabilityOffset_MapPhysical = 6, CapabilityOffset_MapIO = 7, + CapabilityOffset_MapRegion = 10, CapabilityOffset_Interrupt = 11, CapabilityOffset_ProgramType = 13, CapabilityOffset_KernelVersion = 14, @@ -46,6 +47,7 @@ enum class CapabilityType : u32 { Syscall = (1U << CapabilityOffset_Syscall) - 1, MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1, MapIO = (1U << CapabilityOffset_MapIO) - 1, + MapRegion = (1U << CapabilityOffset_MapRegion) - 1, Interrupt = (1U << CapabilityOffset_Interrupt) - 1, ProgramType = (1U << CapabilityOffset_ProgramType) - 1, KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1, @@ -187,6 +189,8 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s return HandleSyscallFlags(set_svc_bits, flag); case CapabilityType::MapIO: return HandleMapIOFlags(flag, page_table); + case CapabilityType::MapRegion: + return HandleMapRegionFlags(flag, page_table); case CapabilityType::Interrupt: return HandleInterruptFlags(flag); case CapabilityType::ProgramType: @@ -298,6 +302,11 @@ ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, KPageTable& page_tab return RESULT_SUCCESS; } +ResultCode ProcessCapabilities::HandleMapRegionFlags(u32 flags, KPageTable& page_table) { + // TODO(Lioncache): Implement once the memory manager can handle this. + return RESULT_SUCCESS; +} + ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) { constexpr u32 interrupt_ignore_value = 0x3FF; 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: /// Handles flags related to mapping IO pages. ResultCode HandleMapIOFlags(u32 flags, KPageTable& page_table); + /// Handles flags related to mapping physical memory regions. + ResultCode HandleMapRegionFlags(u32 flags, KPageTable& page_table); + /// Handles flags related to the interrupt capability flags. ResultCode HandleInterruptFlags(u32 flags); 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, { std::unique_lock lock{queue_mutex}; + auto* server_session{&session.GetServerSession()}; + // Open a reference to the session to ensure it is not closes while the service request // completes asynchronously. - session.Open(); + server_session->Open(); - requests.emplace([session_ptr{&session}, context{std::move(context)}]() { + requests.emplace([server_session, context{std::move(context)}]() { // Close the reference. - SCOPE_EXIT({ session_ptr->Close(); }); - - // If the session has been closed, we are done. - if (session_ptr->IsServerClosed()) { - return; - } + SCOPE_EXIT({ server_session->Close(); }); // Complete the service request. - KScopedAutoObject server_session{&session_ptr->GetServerSession()}; server_session->CompleteSyncRequest(*context); }); } 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 { private: static Derived* Allocate(KernelCore& kernel) { - return new Derived(kernel); + return kernel.SlabHeap<Derived>().AllocateWithKernel(kernel); } static void Free(KernelCore& kernel, Derived* obj) { - delete obj; + kernel.SlabHeap<Derived>().Free(obj); } 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 @@ #include <algorithm> #include <array> -#include "common/common_paths.h" #include "common/common_types.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "common/swap.h" @@ -41,9 +41,9 @@ constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; // Thumbnails are hard coded to be at least this size constexpr std::size_t THUMBNAIL_SIZE = 0x24000; -static std::string GetImagePath(Common::UUID uuid) { - return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; +static std::filesystem::path GetImagePath(Common::UUID uuid) { + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); } static constexpr u32 SanitizeJPEGSize(std::size_t size) { @@ -328,7 +328,8 @@ protected: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - const Common::FS::IOFile image(GetImagePath(user_id), "rb"); + const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile); if (!image.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); @@ -339,7 +340,10 @@ protected: const u32 size = SanitizeJPEGSize(image.GetSize()); std::vector<u8> buffer(size); - image.ReadBytes(buffer.data(), buffer.size()); + + if (image.Read(buffer) != buffer.size()) { + LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image."); + } ctx.WriteBuffer(buffer); rb.Push<u32>(size); @@ -350,7 +354,8 @@ protected: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - const Common::FS::IOFile image(GetImagePath(user_id), "rb"); + const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile); if (!image.IsOpen()) { LOG_WARNING(Service_ACC, @@ -415,10 +420,11 @@ protected: ProfileData data; std::memcpy(&data, user_data.data(), sizeof(ProfileData)); - Common::FS::IOFile image(GetImagePath(user_id), "wb"); + Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile); - if (!image.IsOpen() || !image.Resize(image_data.size()) || - image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || + if (!image.IsOpen() || !image.SetSize(image_data.size()) || + image.Write(image_data) != image_data.size() || !profile_manager.SetProfileBaseAndData(user_id, base, data)) { LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); 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 @@ #include <fmt/format.h> -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "core/hle/service/acc/profile_manager.h" @@ -36,7 +38,7 @@ constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, u32(-1)); constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, u32(-2)); constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); -constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/"; +constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "system/save/8000000000000010/su/avators"; ProfileManager::ProfileManager() { ParseUserSaveFile(); @@ -325,8 +327,9 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& } void ProfileManager::ParseUserSaveFile() { - const FS::IOFile save( - FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb"); + const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH / + "profiles.dat"); + const FS::IOFile save(save_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); if (!save.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " @@ -335,7 +338,7 @@ void ProfileManager::ParseUserSaveFile() { } ProfileDataRaw data; - if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) { + if (!save.ReadObject(data)) { LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user " "'yuzu' with random UUID."); return; @@ -372,31 +375,27 @@ void ProfileManager::WriteUserSaveFile() { }; } - const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010"; - if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) { - FS::Delete(raw_path); + const auto raw_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / "system/save/8000000000000010"); + if (FS::IsFile(raw_path) && !FS::RemoveFile(raw_path)) { + return; } - const auto path = - FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; + const auto save_path(FS::GetYuzuPath(FS::YuzuPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH / + "profiles.dat"); - if (!FS::CreateFullPath(path)) { + if (!FS::CreateParentDirs(save_path)) { LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " "nand/system/save/8000000000000010/su/avators to mitigate this " "issue."); return; } - FS::IOFile save(path, "wb"); + FS::IOFile save(save_path, FS::FileAccessMode::Write, FS::FileType::BinaryFile); - if (!save.IsOpen()) { + if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) { LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " "made in current session will be saved."); - return; } - - save.Resize(sizeof(ProfileDataRaw)); - save.WriteBytes(&raw, sizeof(ProfileDataRaw)); } }; // 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 @@ // Refer to the license.txt file included. #include "common/assert.h" -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" @@ -135,14 +136,10 @@ void ExtractSharedFonts(Core::System& system) { "FontNintendoExtended2.ttf", }; - for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { - const auto fonts_dir = Common::FS::SanitizePath( - fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), - Common::FS::DirectorySeparator::PlatformDefault); + const auto fonts_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts"; - const auto font_file_path = - Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]), - Common::FS::DirectorySeparator::PlatformDefault); + for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { + const auto font_file_path = fonts_dir / DECRYPTED_SHARED_FONTS[i]; if (Common::FS::Exists(font_file_path)) { continue; @@ -197,8 +194,8 @@ void ExtractSharedFonts(Core::System& system) { FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>( std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); - const auto temp_dir = - system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite); + const auto temp_dir = system.GetFilesystem()->CreateDirectory( + Common::FS::PathToUTF8String(fonts_dir), FileSys::Mode::ReadWrite); const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); @@ -312,13 +309,14 @@ void WebBrowser::Execute() { } void WebBrowser::ExtractOfflineRomFS() { - LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir); + LOG_DEBUG(Service_AM, "Extracting RomFS to {}", + Common::FS::PathToUTF8String(offline_cache_dir)); const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - const auto temp_dir = - system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite); + const auto temp_dir = system.GetFilesystem()->CreateDirectory( + Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite); FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); } @@ -397,15 +395,12 @@ void WebBrowser::InitializeOffline() { "system_data", }; - offline_cache_dir = Common::FS::SanitizePath( - fmt::format("{}/offline_web_applet_{}/{:016X}", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), - RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id), - Common::FS::DirectorySeparator::PlatformDefault); + offline_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + fmt::format("offline_web_applet_{}/{:016X}", + RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id); - offline_document = Common::FS::SanitizePath( - fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path), - Common::FS::DirectorySeparator::PlatformDefault); + offline_document = Common::FS::ConcatPathSafe( + offline_cache_dir, fmt::format("{}/{}", additional_paths, document_path)); } void WebBrowser::InitializeShare() {} @@ -429,8 +424,7 @@ void WebBrowser::ExecuteLogin() { } void WebBrowser::ExecuteOffline() { - const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document), - Common::FS::DirectorySeparator::PlatformDefault); + const auto main_url = GetMainURL(Common::FS::PathToUTF8String(offline_document)); if (!Common::FS::Exists(main_url)) { offline_romfs = GetOfflineRomFS(system, title_id, nca_type); @@ -444,10 +438,11 @@ void WebBrowser::ExecuteOffline() { } } - LOG_INFO(Service_AM, "Opening offline document at {}", offline_document); + LOG_INFO(Service_AM, "Opening offline document at {}", + Common::FS::PathToUTF8String(offline_document)); frontend.OpenLocalWebPage( - offline_document, [this] { ExtractOfflineRomFS(); }, + Common::FS::PathToUTF8String(offline_document), [this] { ExtractOfflineRomFS(); }, [this](WebExitReason exit_reason, std::string last_url) { WebBrowserExit(exit_reason, last_url); }); 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 @@ #pragma once +#include <filesystem> #include <optional> #include "common/common_funcs.h" @@ -75,8 +76,8 @@ private: u64 title_id{}; FileSys::ContentRecordType nca_type{}; - std::string offline_cache_dir; - std::string offline_document; + std::filesystem::path offline_cache_dir; + std::filesystem::path offline_document; FileSys::VirtualFile offline_romfs; 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 @@ #pragma GCC diagnostic pop #endif +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/logging/backend.h" #include "common/logging/log.h" @@ -96,14 +99,14 @@ constexpr u32 PORT = 443; constexpr u32 TIMEOUT_SECONDS = 30; [[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB -std::string GetBINFilePath(u64 title_id) { - return fmt::format("{}bcat/{:016X}/launchparam.bin", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); +std::filesystem::path GetBINFilePath(u64 title_id) { + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / + fmt::format("{:016X}/launchparam.bin", title_id); } -std::string GetZIPFilePath(u64 title_id) { - return fmt::format("{}bcat/{:016X}/data.zip", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id); +std::filesystem::path GetZIPFilePath(u64 title_id) { + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / + fmt::format("{:016X}/data.zip", title_id); } // 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, class Boxcat::Client { public: - Client(std::string path_, u64 title_id_, u64 build_id_) + Client(std::filesystem::path path_, u64 title_id_, u64 build_id_) : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {} DownloadResult DownloadDataZip() { @@ -217,10 +220,11 @@ private: }; if (Common::FS::Exists(path)) { - Common::FS::IOFile file{path, "rb"}; + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; if (file.IsOpen()) { std::vector<u8> bytes(file.GetSize()); - file.ReadBytes(bytes.data(), bytes.size()); + void(file.Read(bytes)); const auto digest = DigestFile(bytes); headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); } @@ -247,14 +251,23 @@ private: return DownloadResult::InvalidContentType; } - Common::FS::CreateFullPath(path); - Common::FS::IOFile file{path, "wb"}; - if (!file.IsOpen()) + if (!Common::FS::CreateDirs(path)) { return DownloadResult::GeneralFSError; - if (!file.Resize(response->body.size())) + } + + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + return DownloadResult::GeneralFSError; + } + + if (!file.SetSize(response->body.size())) { return DownloadResult::GeneralFSError; - if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size()) + } + + if (file.Write(response->body) != response->body.size()) { return DownloadResult::GeneralFSError; + } return DownloadResult::Success; } @@ -267,7 +280,7 @@ private: } std::unique_ptr<httplib::SSLClient> client; - std::string path; + std::filesystem::path path; u64 title_id; u64 build_id; }; @@ -291,7 +304,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe return; } - const auto zip_path{GetZIPFilePath(title.title_id)}; + const auto zip_path = GetZIPFilePath(title.title_id); Boxcat::Client client{zip_path, title.title_id, title.build_id}; progress.StartConnecting(); @@ -301,7 +314,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - Common::FS::Delete(zip_path); + void(Common::FS::RemoveFile(zip_path)); } HandleDownloadDisplayResult(applet_manager, res); @@ -311,11 +324,13 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe progress.StartProcessingDataList(); - Common::FS::IOFile zip{zip_path, "rb"}; + Common::FS::IOFile zip{zip_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; const auto size = zip.GetSize(); std::vector<u8> bytes(size); - if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { - LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); + if (!zip.IsOpen() || size == 0 || zip.Read(bytes) != bytes.size()) { + LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", + Common::FS::PathToUTF8String(zip_path)); progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); return; } @@ -419,19 +434,19 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { } std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { - const auto path{GetBINFilePath(title.title_id)}; + const auto bin_file_path = GetBINFilePath(title.title_id); if (Settings::values.bcat_boxcat_local) { LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); } else { - Client launch_client{path, title.title_id, title.build_id}; + Client launch_client{bin_file_path, title.title_id, title.build_id}; const auto res = launch_client.DownloadLaunchParam(); if (res != DownloadResult::Success) { LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - Common::FS::Delete(path); + void(Common::FS::RemoveFile(bin_file_path)); } HandleDownloadDisplayResult(applet_manager, res); @@ -439,12 +454,13 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) } } - Common::FS::IOFile bin{path, "rb"}; + Common::FS::IOFile bin{bin_file_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; const auto size = bin.GetSize(); std::vector<u8> bytes(size); - if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { + if (!bin.IsOpen() || size == 0 || bin.Read(bytes) != bytes.size()) { LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", - path); + Common::FS::PathToUTF8String(bin_file_path)); return std::nullopt; } 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 @@ #include <cstring> #include <ctime> #include <fmt/chrono.h> -#include "common/file_util.h" #include "common/logging/log.h" #include "common/scm_rev.h" #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 @@ #include <utility> #include "common/assert.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "core/core.h" #include "core/file_sys/bis_factory.h" @@ -728,14 +728,17 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove sdmc_factory = nullptr; } - auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir), - FileSys::Mode::ReadWrite); - auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir), - FileSys::Mode::ReadWrite); - auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir), - FileSys::Mode::ReadWrite); - auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), - FileSys::Mode::ReadWrite); + using YuzuPath = Common::FS::YuzuPath; + const auto rw_mode = FileSys::Mode::ReadWrite; + + auto nand_directory = + vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); + auto sd_directory = + vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode); + auto load_directory = + vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); + auto dump_directory = + vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); if (bis_factory == nullptr) { 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 @@ #include <random> #include "common/assert.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" 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 @@ #include <vector> #include "common/assert.h" -#include "common/common_paths.h" #include "common/common_types.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" #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 addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform, crop_rect}; - system.GetPerfStats().EndGameFrame(); system.GetPerfStats().EndSystemFrame(); system.GPU().SwapBuffers(&framebuffer); 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) ASSERT(!port_installed); auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); - port->SetHleHandler(shared_from_this()); + port->SetSessionHandler(shared_from_this()); port_installed = true; } @@ -118,7 +118,7 @@ Kernel::KClientPort& ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel auto* port = Kernel::KPort::Create(kernel); port->Initialize(max_sessions, false, service_name); - port->GetServerPort().SetHleHandler(shared_from_this()); + port->GetServerPort().SetSessionHandler(shared_from_this()); port_installed = true; 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 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_client_port.h" #include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_port.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" +#include "core/hle/kernel/k_server_port.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_session.h" #include "core/hle/service/sm/controller.h" @@ -13,7 +18,7 @@ namespace Service::SM { void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { - ASSERT_MSG(ctx.Session()->IsSession(), "Session is already a domain"); + ASSERT_MSG(!ctx.Session()->IsDomain(), "Session is already a domain"); LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); ctx.Session()->ConvertToDomain(); @@ -29,16 +34,36 @@ void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called"); - auto session = ctx.Session()->GetParent(); + auto& kernel = system.Kernel(); + auto* session = ctx.Session()->GetParent(); + auto* port = session->GetParent()->GetParent(); - // Open a reference to the session to simulate a new one being created. - session->Open(); - session->GetClientSession().Open(); - session->GetServerSession().Open(); + // Reserve a new session from the process resource limit. + Kernel::KScopedResourceReservation session_reservation( + kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); + if (!session_reservation.Succeeded()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(Kernel::ResultLimitReached); + } + // Create a new session. + auto* clone = Kernel::KSession::Create(kernel); + clone->Initialize(&port->GetClientPort(), session->GetName()); + + // Commit the session reservation. + session_reservation.Commit(); + + // Enqueue the session with the named port. + port->EnqueueSession(&clone->GetServerSession()); + + // Set the session request manager. + clone->GetServerSession().SetSessionRequestManager( + session->GetServerSession().GetSessionRequestManager()); + + // We succeeded. IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(session->GetClientSession()); + rb.PushMoveObjects(clone->GetClientSession()); } 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& IPC::RequestParser rp{ctx}; std::string name(PopServiceName(rp)); + // Find the named port. auto result = service_manager.GetServicePort(name); if (result.Failed()) { LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.Code().raw); return result.Code(); } - auto* port = result.Unwrap(); - // Kernel::KScopedResourceReservation session_reservation( - // kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); - // R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached); + // Reserve a new session from the process resource limit. + Kernel::KScopedResourceReservation session_reservation( + kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); + R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached); + // Create a new session. auto* session = Kernel::KSession::Create(kernel); session->Initialize(&port->GetClientPort(), std::move(name)); // Commit the session reservation. - // session_reservation.Commit(); + session_reservation.Commit(); - if (port->GetServerPort().GetHLEHandler()) { - port->GetServerPort().GetHLEHandler()->ClientConnected(&session->GetServerSession()); - } else { - port->EnqueueSession(&session->GetServerSession()); - } + // Enqueue the session with the named port. + port->EnqueueSession(&session->GetServerSession()); LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); + return MakeResult(&session->GetClientSession()); } 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: if (port == nullptr) { return nullptr; } - return std::static_pointer_cast<T>(port->GetServerPort().GetHLEHandler()); + return std::static_pointer_cast<T>(port->GetServerPort().GetSessionRequestHandler()); } 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 @@ #include <cinttypes> #include <cstring> #include "common/common_funcs.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "core/core.h" #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 @@ #include <string> #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "core/hle/kernel/code_set.h" #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 @@ #include <ostream> #include <string> #include "common/concepts.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/string_util.h" #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 @@ #include <utility> -#include "common/file_util.h" #include "common/logging/log.h" #include "core/core.h" #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 @@ #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/file_util.h" #include "common/logging/log.h" #include "common/settings.h" #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 @@ #include <vector> #include "common/common_funcs.h" -#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #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 { return nullptr; } - u8* GetKernelBuffer(VAddr start_vaddr, size_t size) { - // TODO(bunnei): This is just a workaround until we have kernel memory layout mapped & - // managed. Until then, we use this to allocate and access kernel memory regions. - - auto search = kernel_memory_regions.find(start_vaddr); - if (search != kernel_memory_regions.end()) { - return search->second.get(); - } - - std::unique_ptr<u8[]> new_memory_region{new u8[size]}; - u8* raw_ptr = new_memory_region.get(); - kernel_memory_regions[start_vaddr] = std::move(new_memory_region); - - return raw_ptr; - } - u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -727,7 +711,6 @@ struct Memory::Impl { } Common::PageTable* current_page_table = nullptr; - std::unordered_map<VAddr, std::unique_ptr<u8[]>> kernel_memory_regions; Core::System& system; }; @@ -765,10 +748,6 @@ u8* Memory::GetPointer(VAddr vaddr) { return impl->GetPointer(vaddr); } -u8* Memory::GetKernelBuffer(VAddr start_vaddr, size_t size) { - return impl->GetKernelBuffer(start_vaddr, size); -} - const u8* Memory::GetPointer(VAddr vaddr) const { return impl->GetPointer(vaddr); } 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: */ u8* GetPointer(VAddr vaddr); - /** - * Gets a pointer to the start of a kernel heap allocated memory region. Will allocate one if it - * does not already exist. - * - * @param start_vaddr Start virtual address for the memory region. - * @param size Size of the memory region. - */ - u8* GetKernelBuffer(VAddr start_vaddr, size_t size); - template <typename T> T* GetPointer(VAddr vaddr) { 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 @@ #include <thread> #include <fmt/chrono.h> #include <fmt/format.h> -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/math_util.h" #include "common/settings.h" #include "core/perf_stats.h" @@ -38,12 +40,17 @@ PerfStats::~PerfStats() { std::ostringstream stream; std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, std::ostream_iterator<double>(stream, "\n")); - const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir); + + const auto path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir); // %F Date format expanded is "%Y-%m-%d" - const std::string filename = - fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); - Common::FS::IOFile file(filename, "w"); - file.WriteString(stream.str()); + const auto filename = fmt::format("{:%F-%H-%M}_{:016X}.csv", *std::localtime(&t), title_id); + const auto filepath = path / filename; + + if (Common::FS::CreateParentDir(filepath)) { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write, + Common::FS::FileType::TextFile); + void(file.WriteString(stream.str())); + } } void PerfStats::BeginSystemFrame() { @@ -69,9 +76,7 @@ void PerfStats::EndSystemFrame() { } void PerfStats::EndGameFrame() { - std::lock_guard lock{object_mutex}; - - game_frames += 1; + game_frames.fetch_add(1, std::memory_order_relaxed); } double PerfStats::GetMeanFrametime() const { @@ -94,10 +99,11 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us const auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval; - + const auto current_frames = static_cast<double>(game_frames.load(std::memory_order_relaxed)); + const auto current_fps = current_frames / interval; const PerfStatsResults results{ .system_fps = static_cast<double>(system_frames) / interval, - .game_fps = static_cast<double>(game_frames) / interval, + .average_game_fps = (current_fps + previous_fps) / 2.0, .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / static_cast<double>(system_frames), .emulation_speed = system_us_per_second.count() / 1'000'000.0, @@ -108,7 +114,8 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us reset_point_system_us = current_system_time_us; accumulated_frametime = Clock::duration::zero(); system_frames = 0; - game_frames = 0; + game_frames.store(0, std::memory_order_relaxed); + previous_fps = current_fps; return results; } 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 @@ #pragma once #include <array> +#include <atomic> #include <chrono> #include <cstddef> #include <mutex> @@ -15,8 +16,8 @@ namespace Core { struct PerfStatsResults { /// System FPS (LCD VBlanks) in Hz double system_fps; - /// Game FPS (GSP frame submissions) in Hz - double game_fps; + /// Average game FPS (GPU frame renders) in Hz + double average_game_fps; /// Walltime per system frame, in seconds, excluding any waits double frametime; /// Ratio of walltime / emulated time elapsed @@ -72,7 +73,7 @@ private: /// Cumulative number of system frames (LCD VBlanks) presented since last reset u32 system_frames = 0; /// Cumulative number of game frames (GSP frame submissions) since last reset - u32 game_frames = 0; + std::atomic<u32> game_frames = 0; /// Point when the previous system frame ended Clock::time_point previous_frame_end = reset_point; @@ -80,6 +81,8 @@ private: Clock::time_point frame_begin = reset_point; /// Total visible duration (including frame-limiting, etc.) of the previous system frame Clock::duration previous_frame_length = Clock::duration::zero(); + /// Previously computed fps + double previous_fps = 0; }; 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 @@ #include <fmt/ostream.h> #include <nlohmann/json.hpp> -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/scm_rev.h" #include "common/settings.h" @@ -26,10 +28,9 @@ namespace { -std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { - return fmt::format("{}{}/{:016X}_{}.json", - Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id, - timestamp); +std::filesystem::path GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / type / + fmt::format("{:016X}_{}.json", title_id, timestamp); } std::string GetTimestamp() { @@ -39,14 +40,16 @@ std::string GetTimestamp() { using namespace nlohmann; -void SaveToFile(json json, const std::string& filename) { - if (!Common::FS::CreateFullPath(filename)) { - LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); +void SaveToFile(json json, const std::filesystem::path& filename) { + if (!Common::FS::CreateParentDirs(filename)) { + LOG_ERROR(Core, "Failed to create path for '{}' to save report!", + Common::FS::PathToUTF8String(filename)); return; } - std::ofstream file( - Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault)); + std::ofstream file; + Common::FS::OpenFileStream(file, filename, std::ios_base::out | std::ios_base::trunc); + file << std::setw(4) << json << std::endl; } 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 @@ #include "common/assert.h" #include "common/common_types.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" @@ -72,31 +74,41 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) { u64 GetTelemetryId() { u64 telemetry_id{}; - const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + - "telemetry_id"}; + const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; bool generate_new_id = !Common::FS::Exists(filename); + if (!generate_new_id) { - Common::FS::IOFile file(filename, "rb"); + Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); + LOG_ERROR(Core, "failed to open telemetry_id: {}", + Common::FS::PathToUTF8String(filename)); return {}; } - file.ReadBytes(&telemetry_id, sizeof(u64)); - if (telemetry_id == 0) { + + if (!file.ReadObject(telemetry_id) || telemetry_id == 0) { LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); generate_new_id = true; } } if (generate_new_id) { - Common::FS::IOFile file(filename, "wb"); + Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); + LOG_ERROR(Core, "failed to open telemetry_id: {}", + Common::FS::PathToUTF8String(filename)); return {}; } + telemetry_id = GenerateTelemetryId(); - file.WriteBytes(&telemetry_id, sizeof(u64)); + + if (!file.WriteObject(telemetry_id)) { + LOG_ERROR(Core, "Failed to write telemetry_id to file."); + } } return telemetry_id; @@ -104,15 +116,20 @@ u64 GetTelemetryId() { u64 RegenerateTelemetryId() { const u64 new_telemetry_id{GenerateTelemetryId()}; - const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) + - "telemetry_id"}; + const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; + + Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; - Common::FS::IOFile file(filename, "wb"); if (!file.IsOpen()) { - LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); + LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); return {}; } - file.WriteBytes(&new_telemetry_id, sizeof(u64)); + + if (!file.WriteObject(new_telemetry_id)) { + LOG_ERROR(Core, "Failed to write telemetry_id to file."); + } + return new_telemetry_id; } 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) { return joystick->GetSDLJoystick() == sdl_joystick; }); - (*joystick_it)->SetSDLJoystick(nullptr, nullptr); + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(nullptr, nullptr); + } } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -1315,51 +1317,51 @@ public: void Start(const std::string& device_id) override { SDLPoller::Start(device_id); // Reset stored axes - analog_x_axis = -1; - analog_y_axis = -1; + first_axis = -1; } Common::ParamPackage GetNextInput() override { SDL_Event event; while (state.event_queue.Pop(event)) { - // Filter out axis events that are below a threshold - if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - if (event.type == SDL_JOYAXISMOTION) { - const auto axis = event.jaxis.axis; - // In order to return a complete analog param, we need inputs for both axes. - // First we take the x-axis (horizontal) input, then the y-axis (vertical) input. - if (analog_x_axis == -1) { - analog_x_axis = axis; - } else if (analog_y_axis == -1 && analog_x_axis != axis) { - analog_y_axis = axis; - } - } else { - // If the press wasn't accepted as a joy axis, check for a button press + if (event.type != SDL_JOYAXISMOTION) { + // Check for a button press auto button_press = button_poller.FromEvent(event); if (button_press) { return *button_press; } + continue; + } + const auto axis = event.jaxis.axis; + + // Filter out axis events that are below a threshold + if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + continue; + } + + // Filter out axis events that are the same + if (first_axis == axis) { + continue; + } + + // In order to return a complete analog param, we need inputs for both axes. + // If the first axis isn't set we set the value then wait till next event + if (first_axis == -1) { + first_axis = axis; + continue; } - } - if (analog_x_axis != -1 && analog_y_axis != -1) { if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - analog_x_axis, analog_y_axis); - analog_x_axis = -1; - analog_y_axis = -1; + first_axis, axis); + first_axis = -1; return params; } } - return {}; } private: - int analog_x_axis = -1; - int analog_y_axis = -1; + int first_axis = -1; SDLButtonPoller button_poller; }; } // 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: case Type::PadData: { Response::PadData pad_data; std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); + SanitizeMotion(pad_data); callback.pad_data(std::move(pad_data)); break; } @@ -114,6 +115,28 @@ private: StartSend(timer.expiry()); } + void SanitizeMotion(Response::PadData& data) { + // Zero out any non number value + if (!std::isnormal(data.gyro.pitch)) { + data.gyro.pitch = 0; + } + if (!std::isnormal(data.gyro.roll)) { + data.gyro.roll = 0; + } + if (!std::isnormal(data.gyro.yaw)) { + data.gyro.yaw = 0; + } + if (!std::isnormal(data.accel.x)) { + data.accel.x = 0; + } + if (!std::isnormal(data.accel.y)) { + data.accel.y = 0; + } + if (!std::isnormal(data.accel.z)) { + data.accel.z = 0; + } + } + SocketCallback callback; boost::asio::io_service io_service; 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 @@ #include <memory> #include <string> -#include "common/file_util.h" #include "core/core.h" #include "core/core_timing.h" 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 const VAddr cpu_addr = binding.cpu_addr; const u32 size = binding.size; Buffer& buffer = slot_buffers[binding.buffer_id]; - if (size <= uniform_buffer_skip_cache_size && !buffer.IsRegionGpuModified(cpu_addr, size)) { + const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && + size <= uniform_buffer_skip_cache_size && + !buffer.IsRegionGpuModified(cpu_addr, size); + if (use_fast_buffer) { if constexpr (IS_OPENGL) { if (runtime.HasFastBufferSubData()) { // 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 @@ #include "core/frontend/emu_window.h" #include "core/hardware_interrupt_manager.h" #include "core/memory.h" +#include "core/perf_stats.h" #include "video_core/engines/fermi_2d.h" #include "video_core/engines/kepler_compute.h" #include "video_core/engines/kepler_memory.h" @@ -191,6 +192,10 @@ u64 GPU::GetTicks() const { return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den; } +void GPU::RendererFrameEndNotify() { + system.GetPerfStats().EndGameFrame(); +} + void GPU::FlushCommands() { rasterizer->FlushCommands(); } 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: return use_nvdec; } + void RendererFrameEndNotify(); + enum class FenceOperation : u32 { Acquire = 0, 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() { has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = is_amd; has_precise_bug = TestPreciseBug(); - has_broken_texture_view_formats = is_amd || is_intel; + has_broken_texture_view_formats = is_amd || (!is_linux && is_intel); has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2; has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory; 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 @@ #include <fmt/format.h> #include "common/assert.h" -#include "common/common_paths.h" #include "common/common_types.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/scm_rev.h" #include "common/settings.h" @@ -26,11 +27,7 @@ using Tegra::Engines::ShaderType; using VideoCommon::Shader::BindlessSamplerMap; using VideoCommon::Shader::BoundSamplerMap; using VideoCommon::Shader::KeyMap; - -namespace { - using VideoCommon::Shader::SeparateSamplerKey; - using ShaderCacheVersionHash = std::array<u8, 64>; struct ConstBufferKey { @@ -58,6 +55,8 @@ struct BindlessSamplerEntry { Tegra::Engines::SamplerDescriptor sampler; }; +namespace { + constexpr u32 NativeVersion = 21; ShaderCacheVersionHash GetShaderCacheVersionHash() { @@ -74,22 +73,20 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default; ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { - if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { + if (!file.ReadObject(type)) { return false; } u32 code_size; u32 code_size_b; - if (file.ReadBytes(&code_size, sizeof(u32)) != sizeof(u32) || - file.ReadBytes(&code_size_b, sizeof(u32)) != sizeof(u32)) { + if (!file.ReadObject(code_size) || !file.ReadObject(code_size_b)) { return false; } code.resize(code_size); code_b.resize(code_size_b); - - if (file.ReadArray(code.data(), code_size) != code_size) { + if (file.Read(code) != code_size) { return false; } - if (HasProgramA() && file.ReadArray(code_b.data(), code_size_b) != code_size_b) { + if (HasProgramA() && file.Read(code_b) != code_size_b) { return false; } @@ -99,13 +96,12 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { u32 num_bound_samplers; u32 num_separate_samplers; u32 num_bindless_samplers; - if (file.ReadArray(&unique_identifier, 1) != 1 || file.ReadArray(&bound_buffer, 1) != 1 || - file.ReadArray(&is_texture_handler_size_known, 1) != 1 || - file.ReadArray(&texture_handler_size_value, 1) != 1 || - file.ReadArray(&graphics_info, 1) != 1 || file.ReadArray(&compute_info, 1) != 1 || - file.ReadArray(&num_keys, 1) != 1 || file.ReadArray(&num_bound_samplers, 1) != 1 || - file.ReadArray(&num_separate_samplers, 1) != 1 || - file.ReadArray(&num_bindless_samplers, 1) != 1) { + if (!file.ReadObject(unique_identifier) || !file.ReadObject(bound_buffer) || + !file.ReadObject(is_texture_handler_size_known) || + !file.ReadObject(texture_handler_size_value) || !file.ReadObject(graphics_info) || + !file.ReadObject(compute_info) || !file.ReadObject(num_keys) || + !file.ReadObject(num_bound_samplers) || !file.ReadObject(num_separate_samplers) || + !file.ReadObject(num_bindless_samplers)) { return false; } if (is_texture_handler_size_known) { @@ -116,13 +112,10 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { std::vector<BoundSamplerEntry> flat_bound_samplers(num_bound_samplers); std::vector<SeparateSamplerEntry> flat_separate_samplers(num_separate_samplers); std::vector<BindlessSamplerEntry> flat_bindless_samplers(num_bindless_samplers); - if (file.ReadArray(flat_keys.data(), flat_keys.size()) != flat_keys.size() || - file.ReadArray(flat_bound_samplers.data(), flat_bound_samplers.size()) != - flat_bound_samplers.size() || - file.ReadArray(flat_separate_samplers.data(), flat_separate_samplers.size()) != - flat_separate_samplers.size() || - file.ReadArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) != - flat_bindless_samplers.size()) { + if (file.Read(flat_keys) != flat_keys.size() || + file.Read(flat_bound_samplers) != flat_bound_samplers.size() || + file.Read(flat_separate_samplers) != flat_separate_samplers.size() || + file.Read(flat_bindless_samplers) != flat_bindless_samplers.size()) { return false; } for (const auto& entry : flat_keys) { @@ -145,26 +138,25 @@ bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) { } bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { - if (file.WriteObject(static_cast<u32>(type)) != 1 || - file.WriteObject(static_cast<u32>(code.size())) != 1 || - file.WriteObject(static_cast<u32>(code_b.size())) != 1) { + if (!file.WriteObject(static_cast<u32>(type)) || + !file.WriteObject(static_cast<u32>(code.size())) || + !file.WriteObject(static_cast<u32>(code_b.size()))) { return false; } - if (file.WriteArray(code.data(), code.size()) != code.size()) { + if (file.Write(code) != code.size()) { return false; } - if (HasProgramA() && file.WriteArray(code_b.data(), code_b.size()) != code_b.size()) { + if (HasProgramA() && file.Write(code_b) != code_b.size()) { return false; } - if (file.WriteObject(unique_identifier) != 1 || file.WriteObject(bound_buffer) != 1 || - file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) != 1 || - file.WriteObject(texture_handler_size.value_or(0)) != 1 || - file.WriteObject(graphics_info) != 1 || file.WriteObject(compute_info) != 1 || - file.WriteObject(static_cast<u32>(keys.size())) != 1 || - file.WriteObject(static_cast<u32>(bound_samplers.size())) != 1 || - file.WriteObject(static_cast<u32>(separate_samplers.size())) != 1 || - file.WriteObject(static_cast<u32>(bindless_samplers.size())) != 1) { + if (!file.WriteObject(unique_identifier) || !file.WriteObject(bound_buffer) || + !file.WriteObject(static_cast<u8>(texture_handler_size.has_value())) || + !file.WriteObject(texture_handler_size.value_or(0)) || !file.WriteObject(graphics_info) || + !file.WriteObject(compute_info) || !file.WriteObject(static_cast<u32>(keys.size())) || + !file.WriteObject(static_cast<u32>(bound_samplers.size())) || + !file.WriteObject(static_cast<u32>(separate_samplers.size())) || + !file.WriteObject(static_cast<u32>(bindless_samplers.size()))) { return false; } @@ -197,13 +189,10 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const { BindlessSamplerEntry{address.first, address.second, sampler}); } - return file.WriteArray(flat_keys.data(), flat_keys.size()) == flat_keys.size() && - file.WriteArray(flat_bound_samplers.data(), flat_bound_samplers.size()) == - flat_bound_samplers.size() && - file.WriteArray(flat_separate_samplers.data(), flat_separate_samplers.size()) == - flat_separate_samplers.size() && - file.WriteArray(flat_bindless_samplers.data(), flat_bindless_samplers.size()) == - flat_bindless_samplers.size(); + return file.Write(flat_keys) == flat_keys.size() && + file.Write(flat_bound_samplers) == flat_bound_samplers.size() && + file.Write(flat_separate_samplers) == flat_separate_samplers.size() && + file.Write(flat_bindless_samplers) == flat_bindless_samplers.size(); } ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default; @@ -221,7 +210,8 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran return std::nullopt; } - Common::FS::IOFile file(GetTransferablePath(), "rb"); + Common::FS::IOFile file{GetTransferablePath(), Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_INFO(Render_OpenGL, "No transferable shader cache found"); is_usable = true; @@ -229,7 +219,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran } u32 version{}; - if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { + if (!file.ReadObject(version)) { LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); return std::nullopt; } @@ -249,7 +239,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran // Version is valid, load the shaders std::vector<ShaderDiskCacheEntry> entries; - while (file.Tell() < file.GetSize()) { + while (static_cast<u64>(file.Tell()) < file.GetSize()) { ShaderDiskCacheEntry& entry = entries.emplace_back(); if (!entry.Load(file)) { LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); @@ -266,7 +256,8 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled() return {}; } - Common::FS::IOFile file(GetPrecompiledPath(), "rb"); + Common::FS::IOFile file{GetPrecompiledPath(), Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); return {}; @@ -286,7 +277,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo Common::FS::IOFile& file) { // Read compressed file from disk and decompress to virtual precompiled cache file std::vector<u8> compressed(file.GetSize()); - file.ReadBytes(compressed.data(), compressed.size()); + if (file.Read(compressed) != file.GetSize()) { + return std::nullopt; + } const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(compressed); SaveArrayToPrecompiled(decompressed.data(), decompressed.size()); precompiled_cache_virtual_file_offset = 0; @@ -321,9 +314,9 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo } void ShaderDiskCacheOpenGL::InvalidateTransferable() { - if (!Common::FS::Delete(GetTransferablePath())) { + if (!Common::FS::RemoveFile(GetTransferablePath())) { LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", - GetTransferablePath()); + Common::FS::PathToUTF8String(GetTransferablePath())); } InvalidatePrecompiled(); } @@ -332,8 +325,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() { // Clear virtaul precompiled cache file precompiled_cache_virtual_file.Resize(0); - if (!Common::FS::Delete(GetPrecompiledPath())) { - LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); + if (!Common::FS::RemoveFile(GetPrecompiledPath())) { + LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", + Common::FS::PathToUTF8String(GetPrecompiledPath())); } } @@ -398,16 +392,18 @@ Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { const auto transferable_path{GetTransferablePath()}; const bool existed = Common::FS::Exists(transferable_path); - Common::FS::IOFile file(transferable_path, "ab"); + Common::FS::IOFile file{transferable_path, Common::FS::FileAccessMode::Append, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { - LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); + LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", + Common::FS::PathToUTF8String(transferable_path)); return {}; } if (!existed || file.GetSize() == 0) { // If the file didn't exist, write its version - if (file.WriteObject(NativeVersion) != 1) { + if (!file.WriteObject(NativeVersion)) { LOG_ERROR(Render_OpenGL, "Failed to write transferable cache version in path={}", - transferable_path); + Common::FS::PathToUTF8String(transferable_path)); return {}; } } @@ -429,51 +425,54 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() { const std::vector<u8> compressed = Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); - const auto precompiled_path{GetPrecompiledPath()}; - Common::FS::IOFile file(precompiled_path, "wb"); + const auto precompiled_path = GetPrecompiledPath(); + Common::FS::IOFile file{precompiled_path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { - LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); + LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", + Common::FS::PathToUTF8String(precompiled_path)); return; } - if (file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) { + if (file.Write(compressed) != compressed.size()) { LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}", - precompiled_path); + Common::FS::PathToUTF8String(precompiled_path)); } } bool ShaderDiskCacheOpenGL::EnsureDirectories() const { - const auto CreateDir = [](const std::string& dir) { + const auto CreateDir = [](const std::filesystem::path& dir) { if (!Common::FS::CreateDir(dir)) { - LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); + LOG_ERROR(Render_OpenGL, "Failed to create directory={}", + Common::FS::PathToUTF8String(dir)); return false; } return true; }; - return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) && + return CreateDir(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir)) && CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && CreateDir(GetPrecompiledDir()); } -std::string ShaderDiskCacheOpenGL::GetTransferablePath() const { - return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); +std::filesystem::path ShaderDiskCacheOpenGL::GetTransferablePath() const { + return GetTransferableDir() / fmt::format("{}.bin", GetTitleID()); } -std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { - return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); +std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledPath() const { + return GetPrecompiledDir() / fmt::format("{}.bin", GetTitleID()); } -std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { - return GetBaseDir() + DIR_SEP "transferable"; +std::filesystem::path ShaderDiskCacheOpenGL::GetTransferableDir() const { + return GetBaseDir() / "transferable"; } -std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const { - return GetBaseDir() + DIR_SEP "precompiled"; +std::filesystem::path ShaderDiskCacheOpenGL::GetPrecompiledDir() const { + return GetBaseDir() / "precompiled"; } -std::string ShaderDiskCacheOpenGL::GetBaseDir() const { - return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl"; +std::filesystem::path ShaderDiskCacheOpenGL::GetBaseDir() const { + return Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir) / "opengl"; } 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 @@ #pragma once +#include <filesystem> #include <optional> #include <string> #include <tuple> @@ -108,19 +109,19 @@ private: bool EnsureDirectories() const; /// Gets current game's transferable file path - std::string GetTransferablePath() const; + std::filesystem::path GetTransferablePath() const; /// Gets current game's precompiled file path - std::string GetPrecompiledPath() const; + std::filesystem::path GetPrecompiledPath() const; /// Get user's transferable directory path - std::string GetTransferableDir() const; + std::filesystem::path GetTransferableDir() const; /// Get user's precompiled directory path - std::string GetPrecompiledDir() const; + std::filesystem::path GetPrecompiledDir() const; /// Get user's shader directory path - std::string GetBaseDir() const; + std::filesystem::path GetBaseDir() const; /// Get current game's title id 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) { ++m_current_frame; + gpu.RendererFrameEndNotify(); rasterizer.TickFrame(); 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) { if (swapchain.Present(render_semaphore)) { blit_screen.Recreate(); } + gpu.RendererFrameEndNotify(); rasterizer.TickFrame(); } 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 @@ #ifdef HAS_NSIGHT_AFTERMATH #include <mutex> +#include <span> #include <string> #include <string_view> #include <utility> @@ -12,9 +13,10 @@ #include <fmt/format.h> -#include "common/common_paths.h" #include "common/common_types.h" -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/scope_exit.h" #include "video_core/vulkan_common/nsight_aftermath_tracker.h" @@ -46,9 +48,9 @@ NsightAftermathTracker::NsightAftermathTracker() { LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers"); return; } - dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash"; + dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / "gpucrash"; - void(Common::FS::DeleteDirRecursively(dump_dir)); + void(Common::FS::RemoveDirRecursively(dump_dir)); if (!Common::FS::CreateDir(dump_dir)) { LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); return; @@ -60,7 +62,8 @@ NsightAftermathTracker::NsightAftermathTracker() { LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed"); return; } - LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir); + LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", + Common::FS::PathToUTF8String(dump_dir)); initialized = true; } @@ -89,12 +92,15 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const { return; } - Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); + const auto shader_file = dump_dir / fmt::format("source_{:016x}.spv", hash.hash); + + Common::FS::IOFile file{shader_file, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); return; } - if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) { + if (file.Write(spirv) != spirv.size()) { LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash); return; } @@ -129,22 +135,24 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump, return; } - const std::string base_name = [this] { + std::filesystem::path base_name = [this] { const int id = dump_id++; if (id == 0) { - return fmt::format("{}/crash.nv-gpudmp", dump_dir); + return dump_dir / "crash.nv-gpudmp"; } else { - return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id); + return dump_dir / fmt::format("crash_{}.nv-gpudmp", id); } }(); std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); - if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { + if (Common::FS::WriteStringToFile(base_name, Common::FS::FileType::BinaryFile, dump_view) != + gpu_crash_dump_size) { LOG_ERROR(Render_Vulkan, "Failed to write dump file"); return; } const std::string_view json_view(json.data(), json.size()); - if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { + if (Common::FS::WriteStringToFile(base_name.concat(".json"), Common::FS::FileType::TextFile, + json_view) != json.size()) { LOG_ERROR(Render_Vulkan, "Failed to write JSON"); return; } @@ -161,16 +169,17 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_ return; } - const std::string path = - fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]); - Common::FS::IOFile file(path, "wb"); + const auto path = + dump_dir / fmt::format("shader_{:016x}{:016x}.nvdbg", identifier.id[0], identifier.id[1]); + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { - LOG_ERROR(Render_Vulkan, "Failed to create file {}", path); + LOG_ERROR(Render_Vulkan, "Failed to create file {}", Common::FS::PathToUTF8String(path)); return; } - if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) != - shader_debug_info_size) { - LOG_ERROR(Render_Vulkan, "Failed to write file {}", path); + if (file.WriteSpan(std::span(static_cast<const u8*>(shader_debug_info), + shader_debug_info_size)) != shader_debug_info_size) { + LOG_ERROR(Render_Vulkan, "Failed to write file {}", Common::FS::PathToUTF8String(path)); return; } } 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 @@ #pragma once +#include <filesystem> #include <mutex> #include <string> #include <vector> @@ -54,7 +55,7 @@ private: mutable std::mutex mutex; - std::string dump_dir; + std::filesystem::path dump_dir; int dump_id = 0; 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 @@ #include <string> #include "common/dynamic_library.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "video_core/vulkan_common/vulkan_library.h" namespace Vulkan { @@ -18,9 +18,9 @@ Common::DynamicLibrary OpenLibrary() { char* const libvulkan_env = std::getenv("LIBVULKAN_PATH"); if (!libvulkan_env || !library.Open(libvulkan_env)) { // Use the libvulkan.dylib from the application bundle. - const std::string filename = - Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; - void(library.Open(filename.c_str())); + const auto filename = + Common::FS::GetBundleDirectory() / "Contents/Frameworks/libvulkan.dylib"; + void(library.Open(Common::FS::PathToUTF8String(filename).c_str())); } #else 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 @@ #include <QScrollArea> #include <QStandardItemModel> #include <QVBoxLayout> -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/string_util.h" #include "core/constants.h" #include "core/hle/lock.h" @@ -26,9 +26,10 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) { } QString GetImagePath(Common::UUID uuid) { - const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; - return QString::fromStdString(path); + const auto path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); + return QString::fromStdString(Common::FS::PathToUTF8String(path)); } 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( OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), tr("Cancel"), tr("OK"), Qt::AlignCenter); - if (dialog.exec() == QDialog::Accepted) { - emit SubmitNormalText(SwkbdResult::Ok, current_text); + if (dialog.exec() != QDialog::Accepted) { + StartInputThread(); break; } - StartInputThread(); + auto text = ui->topOSK->currentIndex() == 1 + ? ui->text_edit_osk->toPlainText().toStdU16String() + : ui->line_edit_osk->text().toStdU16String(); + + emit SubmitNormalText(SwkbdResult::Ok, std::move(text)); break; } } @@ -480,11 +484,7 @@ void QtSoftwareKeyboardDialog::open() { void QtSoftwareKeyboardDialog::reject() { // Pressing the ESC key in a dialog calls QDialog::reject(). // We will override this behavior to the "Cancel" action on the software keyboard. - if (is_inline) { - emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); - } else { - emit SubmitNormalText(SwkbdResult::Cancel, current_text); - } + TranslateButtonPress(HIDButton::X); } void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { @@ -720,21 +720,9 @@ void QtSoftwareKeyboardDialog::SetTextDrawType() { ui->line_edit_osk->setFocus(); }); - connect(ui->line_edit_osk, &QLineEdit::returnPressed, [this] { - switch (bottom_osk_index) { - case BottomOSKIndex::LowerCase: - ui->button_ok->click(); - break; - case BottomOSKIndex::UpperCase: - ui->button_ok_shift->click(); - break; - case BottomOSKIndex::NumberPad: - ui->button_ok_num->click(); - break; - default: - break; - } - }); + connect( + ui->line_edit_osk, &QLineEdit::returnPressed, this, + [this] { TranslateButtonPress(HIDButton::Plus); }, Qt::QueuedConnection); ui->line_edit_osk->setPlaceholderText( 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 @@ #include <QWebEngineUrlScheme> #endif -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "core/core.h" #include "core/frontend/input_interpreter.h" #include "input_common/keyboard.h" @@ -322,21 +322,25 @@ void QtNXWebEngineView::LoadExtractedFonts() { QWebEngineScript nx_font_css; QWebEngineScript load_nx_font; - const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( - fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); + auto fonts_dir_str = Common::FS::PathToUTF8String( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "fonts/"); + + std::replace(fonts_dir_str.begin(), fonts_dir_str.end(), '\\', '/'); + + const auto fonts_dir = QString::fromStdString(fonts_dir_str); nx_font_css.setName(QStringLiteral("nx_font_css.js")); load_nx_font.setName(QStringLiteral("load_nx_font.js")); nx_font_css.setSourceCode( QString::fromStdString(NX_FONT_CSS) - .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) - .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) - .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) - .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) - .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) - .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); + .arg(fonts_dir + QStringLiteral("FontStandard.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontExtendedChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("FontChineseTraditional.ttf")) + .arg(fonts_dir + QStringLiteral("FontKorean.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended.ttf")) + .arg(fonts_dir + QStringLiteral("FontNintendoExtended2.ttf"))); load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); 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 @@ #include <array> #include <QKeySequence> #include <QSettings> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" @@ -243,27 +243,27 @@ const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{ // clang-format on void Config::Initialize(const std::string& config_name) { + const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); + const auto config_file = fmt::format("{}.ini", config_name); + switch (type) { case ConfigType::GlobalConfig: - qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), - config_name); - FS::CreateFullPath(qt_config_loc); + qt_config_loc = FS::PathToUTF8String(fs_config_loc / config_file); + void(FS::CreateParentDir(qt_config_loc)); qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); Reload(); break; case ConfigType::PerGameConfig: - qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini", - FS::GetUserPath(FS::UserPath::ConfigDir), config_name); - FS::CreateFullPath(qt_config_loc); + qt_config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / config_file); + void(FS::CreateParentDir(qt_config_loc)); qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); Reload(); break; case ConfigType::InputProfile: - qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", - FS::GetUserPath(FS::UserPath::ConfigDir), config_name); - FS::CreateFullPath(qt_config_loc); + qt_config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); + void(FS::CreateParentDir(qt_config_loc)); qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); break; @@ -514,6 +514,13 @@ void Config::ReadControlValues() { ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat(); ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true); + + // Disable docked mode if handheld is selected + const auto controller_type = Settings::values.players.GetValue()[0].controller_type; + if (controller_type == Settings::ControllerType::Handheld) { + Settings::values.use_docked_mode.SetValue(false); + } + ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"), true); ReadSettingGlobal(Settings::values.enable_accurate_vibrations, @@ -591,30 +598,34 @@ void Config::ReadDataStorageValues() { qt_config->beginGroup(QStringLiteral("Data Storage")); Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); - FS::GetUserPath(FS::UserPath::NANDDir, - qt_config - ->value(QStringLiteral("nand_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))) - .toString() - .toStdString()); - FS::GetUserPath(FS::UserPath::SDMCDir, - qt_config - ->value(QStringLiteral("sdmc_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))) - .toString() - .toStdString()); - FS::GetUserPath(FS::UserPath::LoadDir, - qt_config - ->value(QStringLiteral("load_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))) - .toString() - .toStdString()); - FS::GetUserPath(FS::UserPath::DumpDir, - qt_config - ->value(QStringLiteral("dump_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) - .toString() - .toStdString()); + FS::SetYuzuPath( + FS::YuzuPath::NANDDir, + qt_config + ->value(QStringLiteral("nand_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))) + .toString() + .toStdString()); + FS::SetYuzuPath( + FS::YuzuPath::SDMCDir, + qt_config + ->value(QStringLiteral("sdmc_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))) + .toString() + .toStdString()); + FS::SetYuzuPath( + FS::YuzuPath::LoadDir, + qt_config + ->value(QStringLiteral("load_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))) + .toString() + .toStdString()); + FS::SetYuzuPath( + FS::YuzuPath::DumpDir, + qt_config + ->value(QStringLiteral("dump_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))) + .toString() + .toStdString()); Settings::values.gamecard_inserted = ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); Settings::values.gamecard_current_game = @@ -736,10 +747,16 @@ void Config::ReadPathValues() { void Config::ReadCpuValues() { qt_config->beginGroup(QStringLiteral("Cpu")); - if (global) { - Settings::values.cpu_accuracy = static_cast<Settings::CPUAccuracy>( - ReadSetting(QStringLiteral("cpu_accuracy"), 0).toInt()); + ReadSettingGlobal(Settings::values.cpu_accuracy, QStringLiteral("cpu_accuracy"), 0); + + ReadSettingGlobal(Settings::values.cpuopt_unsafe_unfuse_fma, + QStringLiteral("cpuopt_unsafe_unfuse_fma"), true); + ReadSettingGlobal(Settings::values.cpuopt_unsafe_reduce_fp_error, + QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); + ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, + QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); + if (global) { Settings::values.cpuopt_page_tables = ReadSetting(QStringLiteral("cpuopt_page_tables"), true).toBool(); Settings::values.cpuopt_block_linking = @@ -756,13 +773,6 @@ void Config::ReadCpuValues() { ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); Settings::values.cpuopt_reduce_misalign_checks = ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); - - Settings::values.cpuopt_unsafe_unfuse_fma = - ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); - Settings::values.cpuopt_unsafe_reduce_fp_error = - ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); - Settings::values.cpuopt_unsafe_inaccurate_nan = - ReadSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true).toBool(); } qt_config->endGroup(); @@ -811,11 +821,11 @@ void Config::ReadScreenshotValues() { UISettings::values.enable_screenshot_save_as = ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); - FS::GetUserPath( - FS::UserPath::ScreenshotsDir, + FS::SetYuzuPath( + FS::YuzuPath::ScreenshotsDir, qt_config ->value(QStringLiteral("screenshot_path"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))) + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir))) .toString() .toStdString()); @@ -869,17 +879,14 @@ void Config::ReadSystemValues() { } } - bool custom_rtc_enabled; - ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false); - bool custom_rtc_global = - global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool(); - Settings::values.custom_rtc.SetGlobal(custom_rtc_global); - if (global || !custom_rtc_global) { + if (global) { + const auto custom_rtc_enabled = + ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); if (custom_rtc_enabled) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong())); + Settings::values.custom_rtc = + std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); } else { - Settings::values.custom_rtc.SetValue(std::nullopt); + Settings::values.custom_rtc = std::nullopt; } } @@ -1217,17 +1224,17 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); WriteSetting(QStringLiteral("nand_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)), - QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir))); + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); WriteSetting(QStringLiteral("sdmc_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)), - QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir))); + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); WriteSetting(QStringLiteral("load_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)), - QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir))); + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); WriteSetting(QStringLiteral("dump_directory"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), - QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, false); @@ -1313,10 +1320,19 @@ void Config::SavePathValues() { void Config::SaveCpuValues() { qt_config->beginGroup(QStringLiteral("Cpu")); - if (global) { - WriteSetting(QStringLiteral("cpu_accuracy"), - static_cast<int>(Settings::values.cpu_accuracy), 0); + WriteSettingGlobal(QStringLiteral("cpu_accuracy"), + static_cast<u32>(Settings::values.cpu_accuracy.GetValue(global)), + Settings::values.cpu_accuracy.UsingGlobal(), + static_cast<u32>(Settings::CPUAccuracy::Accurate)); + + WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_unfuse_fma"), + Settings::values.cpuopt_unsafe_unfuse_fma, true); + WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), + Settings::values.cpuopt_unsafe_reduce_fp_error, true); + WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), + Settings::values.cpuopt_unsafe_inaccurate_nan, true); + if (global) { WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, true); WriteSetting(QStringLiteral("cpuopt_block_linking"), Settings::values.cpuopt_block_linking, @@ -1331,13 +1347,6 @@ void Config::SaveCpuValues() { WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), Settings::values.cpuopt_reduce_misalign_checks, true); - - WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), - Settings::values.cpuopt_unsafe_unfuse_fma, true); - WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), - Settings::values.cpuopt_unsafe_reduce_fp_error, true); - WriteSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), - Settings::values.cpuopt_unsafe_inaccurate_nan, true); } qt_config->endGroup(); @@ -1392,7 +1401,7 @@ void Config::SaveScreenshotValues() { WriteSetting(QStringLiteral("enable_screenshot_save_as"), UISettings::values.enable_screenshot_save_as); WriteSetting(QStringLiteral("screenshot_path"), - QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir))); + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir))); qt_config->endGroup(); } @@ -1432,14 +1441,14 @@ void Config::SaveSystemValues() { Settings::values.rng_seed.GetValue(global).value_or(0), Settings::values.rng_seed.UsingGlobal(), 0); - WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"), - Settings::values.custom_rtc.GetValue(global).has_value(), - Settings::values.custom_rtc.UsingGlobal(), false); - WriteSettingGlobal( - QStringLiteral("custom_rtc"), - QVariant::fromValue<long long>( - Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()), - Settings::values.custom_rtc.UsingGlobal(), 0); + if (global) { + WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), + false); + WriteSetting(QStringLiteral("custom_rtc"), + QVariant::fromValue<long long>( + Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), + 0); + } WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1); 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: }; // These metatype declarations cannot be in common/settings.h because core is devoid of QT +Q_DECLARE_METATYPE(Settings::CPUAccuracy); Q_DECLARE_METATYPE(Settings::RendererBackend); 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 @@ void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, const CheckState& tracker) { - if (tracker == CheckState::Global) { - setting->SetGlobal(true); - } else { - setting->SetGlobal(false); + if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { setting->SetValue(checkbox->checkState()); + } else if (!Settings::IsConfiguringGlobal()) { + if (tracker == CheckState::Global) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(checkbox->checkState()); + } } } void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox) { - if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - setting->SetGlobal(true); - } else { - setting->SetGlobal(false); - setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); - } -} - -void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, - const QComboBox* combobox) { - if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { - setting->SetGlobal(true); - } else { - setting->SetGlobal(false); - setting->SetValue(static_cast<Settings::RendererBackend>( - combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); + if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) { + setting->SetValue(combobox->currentIndex()); + } else if (!Settings::IsConfiguringGlobal()) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); + } } } @@ -51,27 +48,6 @@ void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, } } -void ConfigurationShared::SetPerGameSetting(QComboBox* combobox, - const Settings::Setting<int>* setting) { - combobox->setCurrentIndex(setting->UsingGlobal() - ? ConfigurationShared::USE_GLOBAL_INDEX - : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET); -} - -void ConfigurationShared::SetPerGameSetting( - QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) { - combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX - : static_cast<int>(setting->GetValue()) + - ConfigurationShared::USE_GLOBAL_OFFSET); -} - -void ConfigurationShared::SetPerGameSetting( - QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) { - combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX - : static_cast<int>(setting->GetValue()) + - ConfigurationShared::USE_GLOBAL_OFFSET); -} - void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) { if (highlighted) { 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; constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; constexpr int USE_GLOBAL_OFFSET = 2; +// CheckBoxes require a tracker for their state since we emulate a tristate CheckBox enum class CheckState { - Off, - On, - Global, - Count, + Off, // Checkbox overrides to off/false + On, // Checkbox overrides to on/true + Global, // Checkbox defers to the global state + Count, // Simply the number of states, not a valid checkbox state }; // Global-aware apply and set functions +// ApplyPerGameSetting, given a Settings::Setting and a Qt UI element, properly applies a Setting void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox, const CheckState& tracker); void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); -void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, - const QComboBox* combobox); -void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting, - const QComboBox* combobox); +// Sets a Qt UI element given a Settings::Setting void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); -void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting); -void SetPerGameSetting(QComboBox* combobox, - const Settings::Setting<Settings::RendererBackend>* setting); -void SetPerGameSetting(QComboBox* combobox, - const Settings::Setting<Settings::GPUAccuracy>* setting); +template <typename Type> +void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<Type>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast<int>(setting->GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +// (Un)highlights a Qt UI element void SetHighlight(QWidget* widget, bool highlighted); + +// Sets up a QCheckBox like a tristate one, given a Setting void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting, CheckState& tracker); void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state, CheckState& tracker); + +// Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls +// InsertGlobalItem void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global); +// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox void InsertGlobalItem(QComboBox* combobox, int global_index); } // 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) { } void ConfigureAudio::ApplyConfiguration() { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, + ui->toggle_audio_stretching, enable_audio_stretching); + if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) @@ -108,19 +111,12 @@ void ConfigureAudio::ApplyConfiguration() { .toStdString(); // Guard if during game and set to game-specific value - if (Settings::values.enable_audio_stretching.UsingGlobal()) { - Settings::values.enable_audio_stretching.SetValue( - ui->toggle_audio_stretching->isChecked()); - } if (Settings::values.volume.UsingGlobal()) { Settings::values.volume.SetValue( static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum()); } } else { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, - ui->toggle_audio_stretching, - enable_audio_stretching); if (ui->volume_combo_box->currentIndex() == 0) { Settings::values.volume.SetGlobal(true); } 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 @@ #include "common/settings.h" #include "core/core.h" #include "ui_configure_cpu.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_cpu.h" ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) { ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, @@ -29,19 +32,29 @@ void ConfigureCpu::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); ui->accuracy->setEnabled(runtime_lock); - ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy)); - UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy)); - ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); - ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma); ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); - ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error); ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); - ui->cpuopt_unsafe_inaccurate_nan->setChecked(Settings::values.cpuopt_unsafe_inaccurate_nan); + + ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); + ui->cpuopt_unsafe_reduce_fp_error->setChecked( + Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); + ui->cpuopt_unsafe_inaccurate_nan->setChecked( + Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); + + if (Settings::IsConfiguringGlobal()) { + ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue())); + } else { + ConfigurationShared::SetPerGameSetting(ui->accuracy, &Settings::values.cpu_accuracy); + ConfigurationShared::SetHighlight(ui->widget_accuracy, + !Settings::values.cpu_accuracy.UsingGlobal()); + } + UpdateGroup(ui->accuracy->currentIndex()); } void ConfigureCpu::AccuracyUpdated(int index) { - if (static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) { + if (Settings::IsConfiguringGlobal() && + static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) { const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"), tr("CPU Debug Mode is only intended for developer " "use. Are you sure you want to enable this?"), @@ -54,16 +67,39 @@ void ConfigureCpu::AccuracyUpdated(int index) { } void ConfigureCpu::UpdateGroup(int index) { - ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) == - Settings::CPUAccuracy::Unsafe); + if (!Settings::IsConfiguringGlobal()) { + index -= ConfigurationShared::USE_GLOBAL_OFFSET; + } + const auto accuracy = static_cast<Settings::CPUAccuracy>(index); + ui->unsafe_group->setVisible(accuracy == Settings::CPUAccuracy::Unsafe); } void ConfigureCpu::ApplyConfiguration() { - Settings::values.cpu_accuracy = - static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); - Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked(); - Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked(); - Settings::values.cpuopt_unsafe_inaccurate_nan = ui->cpuopt_unsafe_inaccurate_nan->isChecked(); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_unfuse_fma, + ui->cpuopt_unsafe_unfuse_fma, + cpuopt_unsafe_unfuse_fma); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_reduce_fp_error, + ui->cpuopt_unsafe_reduce_fp_error, + cpuopt_unsafe_reduce_fp_error); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, + ui->cpuopt_unsafe_inaccurate_nan, + cpuopt_unsafe_inaccurate_nan); + + if (Settings::IsConfiguringGlobal()) { + // Guard if during game and set to game-specific value + if (Settings::values.cpu_accuracy.UsingGlobal()) { + Settings::values.cpu_accuracy.SetValue( + static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex())); + } + } else { + if (ui->accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.cpu_accuracy.SetGlobal(true); + } else { + Settings::values.cpu_accuracy.SetGlobal(false); + Settings::values.cpu_accuracy.SetValue(static_cast<Settings::CPUAccuracy>( + ui->accuracy->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); + } + } } void ConfigureCpu::changeEvent(QEvent* event) { @@ -77,3 +113,25 @@ void ConfigureCpu::changeEvent(QEvent* event) { void ConfigureCpu::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureCpu::SetupPerGameUI() { + if (Settings::IsConfiguringGlobal()) { + return; + } + + ConfigurationShared::SetColoredComboBox( + ui->accuracy, ui->widget_accuracy, + static_cast<u32>(Settings::values.cpu_accuracy.GetValue(true))); + ui->accuracy->removeItem(static_cast<u32>(Settings::CPUAccuracy::DebugMode) + + ConfigurationShared::USE_GLOBAL_OFFSET); + + ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_unfuse_fma, + Settings::values.cpuopt_unsafe_unfuse_fma, + cpuopt_unsafe_unfuse_fma); + ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_reduce_fp_error, + Settings::values.cpuopt_unsafe_reduce_fp_error, + cpuopt_unsafe_reduce_fp_error); + ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, + Settings::values.cpuopt_unsafe_inaccurate_nan, + cpuopt_unsafe_inaccurate_nan); +} 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 @@ #include <QWidget> #include "common/settings.h" +namespace ConfigurationShared { +enum class CheckState; +} + namespace Ui { class ConfigureCpu; } @@ -30,5 +34,11 @@ private: void SetConfiguration(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureCpu> ui; + + ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; + ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; + ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; }; 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 @@ </property> <layout class="QVBoxLayout"> <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel"> - <property name="text"> - <string>Accuracy:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="accuracy"> - <item> + <widget class="QWidget" name="widget_accuracy" native="true"> + <layout class="QHBoxLayout" name="layout_accuracy"> + <item> + <widget class="QLabel" name="label_accuracy"> <property name="text"> - <string>Accurate</string> + <string>Accuracy:</string> </property> - </item> - <item> - <property name="text"> - <string>Unsafe</string> - </property> - </item> - <item> - <property name="text"> - <string>Enable Debug Mode</string> - </property> - </item> - </widget> - </item> - </layout> + </widget> + </item> + <item> + <widget class="QComboBox" name="accuracy"> + <item> + <property name="text"> + <string>Accurate</string> + </property> + </item> + <item> + <property name="text"> + <string>Unsafe</string> + </property> + </item> + <item> + <property name="text"> + <string>Enable Debug Mode</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> </item> <item> - <widget class="QLabel"> - <property name="wordWrap"> - <bool>1</bool> - </property> + <widget class="QLabel" name="label_recommended_accuracy"> <property name="text"> - <string>We recommend setting accuracy to "Accurate".</string> + <string>We recommend setting accuracy to "Accurate".</string> + </property> + <property name="wordWrap"> + <bool>false</bool> </property> </widget> </item> @@ -76,49 +78,49 @@ </property> <layout class="QVBoxLayout"> <item> - <widget class="QLabel"> - <property name="wordWrap"> - <bool>1</bool> - </property> + <widget class="QLabel" name="label_accuracy_description"> <property name="text"> <string>These settings reduce accuracy for speed.</string> </property> + <property name="wordWrap"> + <bool>false</bool> + </property> </widget> </item> <item> <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma"> - <property name="text"> - <string>Unfuse FMA (improve performance on CPUs without FMA)</string> - </property> <property name="toolTip"> <string> <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> </string> </property> + <property name="text"> + <string>Unfuse FMA (improve performance on CPUs without FMA)</string> + </property> </widget> </item> <item> <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error"> - <property name="text"> - <string>Faster FRSQRTE and FRECPE</string> - </property> <property name="toolTip"> <string> <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> </string> </property> + <property name="text"> + <string>Faster FRSQRTE and FRECPE</string> + </property> </widget> </item> <item> <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> - <property name="text"> - <string>Inaccurate NaN handling</string> - </property> <property name="toolTip"> <string> <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> </string> </property> + <property name="text"> + <string>Inaccurate NaN handling</string> + </property> </widget> </item> </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 @@ #include <QDesktopServices> #include <QUrl> -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/settings.h" @@ -20,7 +20,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co connect(ui->open_log_button, &QPushButton::clicked, []() { const auto path = - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir)); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); } 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 @@ #include <QFileDialog> #include <QMessageBox> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "ui_configure_filesystem.h" #include "yuzu/configuration/configure_filesystem.h" @@ -40,14 +40,14 @@ ConfigureFilesystem::~ConfigureFilesystem() = default; void ConfigureFilesystem::setConfiguration() { ui->nand_directory_edit->setText( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir))); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::NANDDir))); ui->sdmc_directory_edit->setText( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir))); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::SDMCDir))); ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); ui->dump_path_edit->setText( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir))); ui->load_path_edit->setText( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir))); ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); @@ -60,13 +60,13 @@ void ConfigureFilesystem::setConfiguration() { } void ConfigureFilesystem::applyConfiguration() { - Common::FS::GetUserPath(Common::FS::UserPath::NANDDir, + Common::FS::SetYuzuPath(Common::FS::YuzuPath::NANDDir, ui->nand_directory_edit->text().toStdString()); - Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir, + Common::FS::SetYuzuPath(Common::FS::YuzuPath::SDMCDir, ui->sdmc_directory_edit->text().toStdString()); - Common::FS::GetUserPath(Common::FS::UserPath::DumpDir, + Common::FS::SetYuzuPath(Common::FS::YuzuPath::DumpDir, ui->dump_path_edit->text().toStdString()); - Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, + Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir, ui->load_path_edit->text().toStdString()); Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); @@ -104,25 +104,26 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) QStringLiteral("NX Gamecard;*.xci")); } else { str = QFileDialog::getExistingDirectory(this, caption, edit->text()); - if (!str.isNull() && str.back() != QDir::separator()) { - str.append(QDir::separator()); - } } - if (str.isEmpty()) + if (str.isNull() || str.isEmpty()) { return; + } + + if (str.back() != QChar::fromLatin1('/')) { + str.append(QChar::fromLatin1('/')); + } edit->setText(str); } void ConfigureFilesystem::ResetMetadata() { - if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list")) { + if (!Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list/")) { QMessageBox::information(this, tr("Reset Metadata Cache"), tr("The metadata cache is already empty.")); - } else if (Common::FS::DeleteDirRecursively( - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list")) { + } else if (Common::FS::RemoveDirRecursively( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list")) { QMessageBox::information(this, tr("Reset Metadata Cache"), tr("The operation completed successfully.")); 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() { } void ConfigureGeneral::ApplyConfiguration() { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, + use_multi_core); + if (Settings::IsConfiguringGlobal()) { UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); @@ -62,13 +65,7 @@ void ConfigureGeneral::ApplyConfiguration() { Qt::Checked); Settings::values.frame_limit.SetValue(ui->frame_limit->value()); } - if (Settings::values.use_multi_core.UsingGlobal()) { - Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); - } } else { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, - ui->use_multi_core, use_multi_core); - bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global; Settings::values.use_frame_limit.SetGlobal(global_frame_limit); Settings::values.frame_limit.SetGlobal(global_frame_limit); @@ -94,6 +91,9 @@ void ConfigureGeneral::RetranslateUI() { void ConfigureGeneral::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { + // Disables each setting if: + // - A game is running (thus settings in use), and + // - A non-global setting is applied. ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); 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() { } void ConfigureGraphics::ApplyConfiguration() { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, + ui->fullscreen_mode_combobox); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, + ui->aspect_ratio_combobox); + + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, + ui->use_disk_shader_cache, use_disk_shader_cache); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, + ui->use_asynchronous_gpu_emulation, + use_asynchronous_gpu_emulation); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, + ui->use_nvdec_emulation, use_nvdec_emulation); + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value if (Settings::values.renderer_backend.UsingGlobal()) { @@ -114,22 +127,6 @@ void ConfigureGraphics::ApplyConfiguration() { if (Settings::values.vulkan_device.UsingGlobal()) { Settings::values.vulkan_device.SetValue(vulkan_device); } - if (Settings::values.fullscreen_mode.UsingGlobal()) { - Settings::values.fullscreen_mode.SetValue(ui->fullscreen_mode_combobox->currentIndex()); - } - if (Settings::values.aspect_ratio.UsingGlobal()) { - Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); - } - if (Settings::values.use_disk_shader_cache.UsingGlobal()) { - Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked()); - } - if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) { - Settings::values.use_asynchronous_gpu_emulation.SetValue( - ui->use_asynchronous_gpu_emulation->isChecked()); - } - if (Settings::values.use_nvdec_emulation.UsingGlobal()) { - Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked()); - } if (Settings::values.bg_red.UsingGlobal()) { Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); @@ -150,19 +147,6 @@ void ConfigureGraphics::ApplyConfiguration() { } } - ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, - ui->fullscreen_mode_combobox); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, - ui->aspect_ratio_combobox); - - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, - ui->use_disk_shader_cache, use_disk_shader_cache); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, - ui->use_asynchronous_gpu_emulation, - use_asynchronous_gpu_emulation); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, - ui->use_nvdec_emulation, use_nvdec_emulation); - if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.bg_red.SetGlobal(true); 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() { ui->gpu_accuracy->currentIndex() - ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, + ui->anisotropic_filtering_combobox); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, + ui->use_assembly_shaders, use_assembly_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, + ui->use_asynchronous_shaders, + use_asynchronous_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, + ui->use_fast_gpu_time, use_fast_gpu_time); + if (Settings::IsConfiguringGlobal()) { // Must guard in case of a during-game configuration when set to be game-specific. if (Settings::values.gpu_accuracy.UsingGlobal()) { Settings::values.gpu_accuracy.SetValue(gpu_accuracy); } - if (Settings::values.use_vsync.UsingGlobal()) { - Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked()); - } - if (Settings::values.use_assembly_shaders.UsingGlobal()) { - Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); - } - if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { - Settings::values.use_asynchronous_shaders.SetValue( - ui->use_asynchronous_shaders->isChecked()); - } - if (Settings::values.use_asynchronous_shaders.UsingGlobal()) { - Settings::values.use_asynchronous_shaders.SetValue( - ui->use_asynchronous_shaders->isChecked()); - } - if (Settings::values.use_fast_gpu_time.UsingGlobal()) { - Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); - } - if (Settings::values.max_anisotropy.UsingGlobal()) { - Settings::values.max_anisotropy.SetValue( - ui->anisotropic_filtering_combobox->currentIndex()); - } } else { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, - ui->anisotropic_filtering_combobox); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, - use_vsync); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, - ui->use_assembly_shaders, use_assembly_shaders); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, - ui->use_asynchronous_shaders, - use_asynchronous_shaders); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, - ui->use_fast_gpu_time, use_fast_gpu_time); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, - ui->anisotropic_filtering_combobox); - if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.gpu_accuracy.SetGlobal(true); } 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 @@ #include <QTimer> #include <QTreeView> -#include "common/common_paths.h" -#include "common/file_util.h" #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -52,6 +50,7 @@ ConfigurePerGame::~ConfigurePerGame() = default; void ConfigurePerGame::ApplyConfiguration() { ui->addonsTab->ApplyConfiguration(); ui->generalTab->ApplyConfiguration(); + ui->cpuTab->ApplyConfiguration(); ui->systemTab->ApplyConfiguration(); ui->graphicsTab->ApplyConfiguration(); 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 @@ <string>System</string> </attribute> </widget> + <widget class="ConfigureCpu" name="cpuTab"> + <attribute name="title"> + <string>CPU</string> + </attribute> + </widget> <widget class="ConfigureGraphics" name="graphicsTab"> <attribute name="title"> <string>Graphics</string> @@ -311,6 +316,12 @@ <header>configuration/configure_per_game_addons.h</header> <container>1</container> </customwidget> + <customwidget> + <class>ConfigureCpu</class> + <extends>QWidget</extends> + <header>configuration/configure_cpu.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <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 @@ #include <QTimer> #include <QTreeView> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" @@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() { std::sort(disabled_addons.begin(), disabled_addons.end()); std::sort(current.begin(), current.end()); if (disabled_addons != current) { - Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); + void(Common::FS::RemoveFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list" / fmt::format("{:016X}.pv.txt", title_id))); } 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 @@ #include <QTreeView> #include <QVBoxLayout> #include "common/assert.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" @@ -34,9 +34,10 @@ constexpr std::array<u8, 107> backup_jpeg{ }; QString GetImagePath(Common::UUID uuid) { - const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; - return QString::fromStdString(path); + const auto path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormatSwitch()); + return QString::fromStdString(Common::FS::PathToUTF8String(path)); } QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) { @@ -281,8 +282,8 @@ void ConfigureProfileManager::SetUserImage() { return; } - const auto raw_path = QString::fromStdString( - Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010"); + const auto raw_path = QString::fromStdString(Common::FS::PathToUTF8String( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000010")); const QFileInfo raw_info{raw_path}; if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { 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 @@ #include <QGraphicsItem> #include <QMessageBox> #include "common/assert.h" -#include "common/file_util.h" #include "common/settings.h" #include "core/core.h" #include "core/hle/service/time/time.h" @@ -65,7 +64,7 @@ void ConfigureSystem::SetConfiguration() { QStringLiteral("%1") .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) .toUpper(); - const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( + const auto rtc_time = Settings::values.custom_rtc.value_or( std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); @@ -73,9 +72,8 @@ void ConfigureSystem::SetConfiguration() { Settings::values.rng_seed.UsingGlobal()); ui->rng_seed_edit->setText(rng_seed); - ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && - Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); if (Settings::IsConfiguringGlobal()) { @@ -109,17 +107,17 @@ void ConfigureSystem::ApplyConfiguration() { // Allow setting custom RTC even if system is powered on, // to allow in-game time to be fast forwarded - if (Settings::values.custom_rtc.UsingGlobal()) { + if (Settings::IsConfiguringGlobal()) { if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + Settings::values.custom_rtc = + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); if (system.IsPoweredOn()) { - const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() + + const s64 posix_time{Settings::values.custom_rtc->count() + Service::Time::TimeManager::GetExternalTimeZoneOffset()}; system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); } } else { - Settings::values.custom_rtc.SetValue(std::nullopt); + Settings::values.custom_rtc = std::nullopt; } } @@ -127,21 +125,14 @@ void ConfigureSystem::ApplyConfiguration() { return; } + ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, + ui->combo_time_zone); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value - if (Settings::values.language_index.UsingGlobal()) { - Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); - } - if (Settings::values.region_index.UsingGlobal()) { - Settings::values.region_index.SetValue(ui->combo_region->currentIndex()); - } - if (Settings::values.time_zone_index.UsingGlobal()) { - Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex()); - } - if (Settings::values.sound_index.UsingGlobal()) { - Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex()); - } - if (Settings::values.rng_seed.UsingGlobal()) { if (ui->rng_seed_checkbox->isChecked()) { Settings::values.rng_seed.SetValue( @@ -151,13 +142,6 @@ void ConfigureSystem::ApplyConfiguration() { } } } else { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, - ui->combo_language); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, - ui->combo_time_zone); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); - switch (use_rng_seed) { case ConfigurationShared::CheckState::On: case ConfigurationShared::CheckState::Off: @@ -177,26 +161,6 @@ void ConfigureSystem::ApplyConfiguration() { case ConfigurationShared::CheckState::Count: break; } - - switch (use_custom_rtc) { - case ConfigurationShared::CheckState::On: - case ConfigurationShared::CheckState::Off: - Settings::values.custom_rtc.SetGlobal(false); - if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); - } else { - Settings::values.custom_rtc.SetValue(std::nullopt); - } - break; - case ConfigurationShared::CheckState::Global: - Settings::values.custom_rtc.SetGlobal(false); - Settings::values.custom_rtc.SetValue(std::nullopt); - Settings::values.custom_rtc.SetGlobal(true); - break; - case ConfigurationShared::CheckState::Count: - break; - } } system.ApplySettings(); @@ -227,8 +191,6 @@ void ConfigureSystem::SetupPerGameUI() { ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); - ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal()); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal()); return; } @@ -246,8 +208,7 @@ void ConfigureSystem::SetupPerGameUI() { ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(), Settings::values.rng_seed.GetValue().has_value(), Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); - ConfigurationShared::SetColoredTristate( - ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(), - Settings::values.custom_rtc.GetValue().has_value(), - Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc); + + ui->custom_rtc_checkbox->setVisible(false); + ui->custom_rtc_edit->setVisible(false); } 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 @@ #include <QDirIterator> #include "common/common_types.h" -#include "common/file_util.h" +#include "common/fs/path_util.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_ui.h" @@ -62,13 +62,16 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur // Set screenshot path to user specification. connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { - const QString& filename = + auto dir = QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."), - QString::fromStdString(Common::FS::GetUserPath( - Common::FS::UserPath::ScreenshotsDir))) + - QDir::separator(); - if (!filename.isEmpty()) { - ui->screenshot_path_edit->setText(filename); + QString::fromStdString(Common::FS::GetYuzuPathString( + Common::FS::YuzuPath::ScreenshotsDir))); + if (!dir.isEmpty()) { + if (dir.back() != QChar::fromLatin1('/')) { + dir.append(QChar::fromLatin1('/')); + } + + ui->screenshot_path_edit->setText(dir); } }); } @@ -84,7 +87,7 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); - Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, + Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir, ui->screenshot_path_edit->text().toStdString()); Core::System::GetInstance().ApplySettings(); } @@ -102,8 +105,8 @@ void ConfigureUi::SetConfiguration() { ui->icon_size_combobox->findData(UISettings::values.icon_size)); ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); - ui->screenshot_path_edit->setText( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir))); + ui->screenshot_path_edit->setText(QString::fromStdString( + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir))); } 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 @@ #include <fmt/format.h> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/input_profiles.h" @@ -14,47 +14,43 @@ namespace FS = Common::FS; namespace { bool ProfileExistsInFilesystem(std::string_view profile_name) { - return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", - FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); + return FS::Exists(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input" / + fmt::format("{}.ini", profile_name)); } -bool IsINI(std::string_view filename) { - const std::size_t index = filename.rfind('.'); - - if (index == std::string::npos) { - return false; - } - - return filename.substr(index) == ".ini"; +bool IsINI(const std::filesystem::path& filename) { + return filename.extension() == ".ini"; } -std::string GetNameWithoutExtension(const std::string& filename) { - const std::size_t index = filename.rfind('.'); - - if (index == std::string::npos) { - return filename; - } - - return filename.substr(0, index); +std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { + return filename.replace_extension(); } } // namespace InputProfiles::InputProfiles() { - const std::string input_profile_loc = - fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); + const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; + + if (!FS::IsDir(input_profile_loc)) { + return; + } - FS::ForeachDirectoryEntry( - nullptr, input_profile_loc, - [this](u64* entries_out, const std::string& directory, const std::string& filename) { - if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { + FS::IterateDirEntries( + input_profile_loc, + [this](const std::filesystem::path& full_path) { + const auto filename = full_path.filename(); + const auto name_without_ext = + Common::FS::PathToUTF8String(GetNameWithoutExtension(filename)); + + if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { map_profiles.insert_or_assign( - GetNameWithoutExtension(filename), - std::make_unique<Config>(GetNameWithoutExtension(filename), - Config::ConfigType::InputProfile)); + name_without_ext, + std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile)); } + return true; - }); + }, + FS::DirEntryFilter::File); } InputProfiles::~InputProfiles() = default; @@ -96,7 +92,7 @@ bool InputProfiles::DeleteProfile(const std::string& profile_name) { } if (!ProfileExistsInFilesystem(profile_name) || - FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { + FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) { map_profiles.erase(profile_name); } 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() { widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetConnectedStatus(players[player].connected); widget->SetControllerType(players[player].controller_type); + widget->repaint(); } 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 @@ #include <QFileInfo> #include <QSettings> -#include "common/common_paths.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "core/core.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" @@ -39,10 +39,11 @@ QString GetGameListCachedObject(const std::string& filename, const std::string& return generator(); } - const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + filename + '.' + ext; + const auto path = + Common::FS::PathToUTF8String(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list" / fmt::format("{}.{}", filename, ext)); - Common::FS::CreateFullPath(path); + void(Common::FS::CreateParentDirs(path)); if (!Common::FS::Exists(path)) { const auto str = generator(); @@ -70,12 +71,15 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject( return generator(); } - const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + filename + ".jpeg"; - const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP + - "game_list" + DIR_SEP + filename + ".appname.txt"; + const auto game_list_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list"; + const auto jpeg_name = fmt::format("{}.jpeg", filename); + const auto app_name = fmt::format("{}.appname.txt", filename); - Common::FS::CreateFullPath(path1); + const auto path1 = Common::FS::PathToUTF8String(game_list_dir / jpeg_name); + const auto path2 = Common::FS::PathToUTF8String(game_list_dir / app_name); + + void(Common::FS::CreateParentDirs(path1)); if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) { const auto [icon, nacp] = generator(); @@ -281,23 +285,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } } -void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, - unsigned int recursion, GameListDir* parent_dir) { +void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, + GameListDir* parent_dir) { auto& system = Core::System::GetInstance(); - const auto callback = [this, target, recursion, parent_dir, - &system](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + const auto callback = [this, target, parent_dir, + &system](const std::filesystem::path& path) -> bool { if (stop_processing) { // Breaks the callback loop. return false; } - const std::string physical_name = directory + DIR_SEP + virtual_name; - const bool is_dir = Common::FS::IsDirectory(physical_name); + const auto physical_name = Common::FS::PathToUTF8String(path); + const auto is_dir = Common::FS::IsDir(path); + if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); + if (!file) { + return true; + } + auto loader = Loader::GetLoader(system, file); if (!loader) { return true; @@ -343,15 +351,19 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa compatibility_list, patch), parent_dir); } - } else if (is_dir && recursion > 0) { + } else if (is_dir) { watch_list.append(QString::fromStdString(physical_name)); - ScanFileSystem(target, physical_name, recursion - 1, parent_dir); } return true; }; - Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback); + if (deep_scan) { + Common::FS::IterateDirEntriesRecursively(dir_path, callback, + Common::FS::DirEntryFilter::All); + } else { + Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); + } } void GameListWorker::run() { @@ -376,9 +388,9 @@ void GameListWorker::run() { auto* const game_list_dir = new GameListDir(game_dir); emit DirEntryReady(game_list_dir); ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), - game_dir.deep_scan ? 256 : 0, game_list_dir); + game_dir.deep_scan, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), - game_dir.deep_scan ? 256 : 0, game_list_dir); + game_dir.deep_scan, game_list_dir); } } 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: PopulateGameList, }; - void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, + void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, GameListDir* parent_dir); 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 #include <QtConcurrent/QtConcurrent> #include <fmt/format.h> -#include "common/common_paths.h" #include "common/detached_tasks.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -178,36 +179,25 @@ static void InitializeLogging() { log_filter.ParseFilterString(Settings::values.log_filter); Log::SetGlobalFilter(log_filter); - const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); - FS::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); + const auto log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); + void(FS::CreateDir(log_dir)); + Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); #ifdef _WIN32 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); #endif } static void RemoveCachedContents() { - const auto offline_fonts = Common::FS::SanitizePath( - fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), - Common::FS::DirectorySeparator::PlatformDefault); - - const auto offline_manual = Common::FS::SanitizePath( - fmt::format("{}/offline_web_applet_manual", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), - Common::FS::DirectorySeparator::PlatformDefault); - const auto offline_legal_information = Common::FS::SanitizePath( - fmt::format("{}/offline_web_applet_legal_information", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), - Common::FS::DirectorySeparator::PlatformDefault); - const auto offline_system_data = Common::FS::SanitizePath( - fmt::format("{}/offline_web_applet_system_data", - Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), - Common::FS::DirectorySeparator::PlatformDefault); - - Common::FS::DeleteDirRecursively(offline_fonts); - Common::FS::DeleteDirRecursively(offline_manual); - Common::FS::DeleteDirRecursively(offline_legal_information); - Common::FS::DeleteDirRecursively(offline_system_data); + const auto cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir); + const auto offline_fonts = cache_dir / "fonts"; + const auto offline_manual = cache_dir / "offline_web_applet_manual"; + const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; + const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; + + void(Common::FS::RemoveDirRecursively(offline_fonts)); + void(Common::FS::RemoveDirRecursively(offline_manual)); + void(Common::FS::RemoveDirRecursively(offline_legal_information)); + void(Common::FS::RemoveDirRecursively(offline_system_data)); } GMainWindow::GMainWindow() @@ -773,10 +763,22 @@ void GMainWindow::InitializeWidgets() { dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); dock_status_button->setFocusPolicy(Qt::NoFocus); connect(dock_status_button, &QPushButton::clicked, [&] { - Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue()); - dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); - OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(), - Settings::values.use_docked_mode.GetValue()); + const bool is_docked = Settings::values.use_docked_mode.GetValue(); + auto& controller_type = Settings::values.players.GetValue()[0].controller_type; + + if (!is_docked && controller_type == Settings::ControllerType::Handheld) { + QMessageBox::warning(this, tr("Invalid config detected"), + tr("Handheld controller can't be used on docked mode. Pro " + "controller will be selected.")); + controller_type = Settings::ControllerType::ProController; + ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get()); + configure_dialog.ApplyConfiguration(); + controller_dialog->refreshConfiguration(); + } + + Settings::values.use_docked_mode.SetValue(!is_docked); + dock_status_button->setChecked(!is_docked); + OnDockedModeChanged(is_docked, !is_docked); }); dock_status_button->setText(tr("DOCK")); dock_status_button->setCheckable(true); @@ -1378,7 +1380,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { game_list->hide(); game_list_placeholder->hide(); } - status_bar_update_timer.start(2000); + status_bar_update_timer.start(500); async_status_button->setDisabled(true); multicore_status_button->setDisabled(true); renderer_status_button->setDisabled(true); @@ -1406,7 +1408,8 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { title_name = metadata.first->GetApplicationName(); } if (res != Loader::ResultStatus::Success || title_name.empty()) { - title_name = Common::FS::GetFilename(filename.toStdString()); + title_name = Common::FS::PathToUTF8String( + std::filesystem::path{filename.toStdU16String()}.filename()); } LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); UpdateWindowTitle(title_name, title_version); @@ -1526,7 +1529,7 @@ void GMainWindow::OnGameListLoadFile(QString game_path) { void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path) { - std::string path; + std::filesystem::path path; QString open_target; auto& system = Core::System::GetInstance(); @@ -1555,7 +1558,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target switch (target) { case GameListOpenTarget::SaveData: { open_target = tr("Save Data"); - const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir); + const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); if (has_user_save) { // User save data @@ -1580,34 +1583,38 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target Service::Account::ProfileManager manager; const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); ASSERT(user_id); - path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - system, FileSys::SaveDataSpaceId::NandUser, - FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0); + + const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, + program_id, user_id->uuid, 0); + + path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path); } else { // Device save data - path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - system, FileSys::SaveDataSpaceId::NandUser, - FileSys::SaveDataType::SaveData, program_id, {}, 0); + const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath( + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, + program_id, {}, 0); + + path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path); } - if (!Common::FS::Exists(path)) { - Common::FS::CreateFullPath(path); - Common::FS::CreateDir(path); + if (!Common::FS::CreateDirs(path)) { + LOG_ERROR(Frontend, "Unable to create the directories for save data"); } break; } case GameListOpenTarget::ModData: { open_target = tr("Mod Data"); - const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir); - path = fmt::format("{}{:016X}", load_dir, program_id); + path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir) / + fmt::format("{:016X}", program_id); break; } default: UNIMPLEMENTED(); } - const QString qpath = QString::fromStdString(path); + const QString qpath = QString::fromStdString(Common::FS::PathToUTF8String(path)); const QDir dir(qpath); if (!dir.exists()) { QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target), @@ -1620,33 +1627,35 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target } void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { - const QString shader_dir = - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); - const QString transferable_shader_cache_folder_path = - shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); - const QString transferable_shader_cache_file_path = - transferable_shader_cache_folder_path + QDir::separator() + - QString::fromStdString(fmt::format("{:016X}.bin", program_id)); - - if (!QFile::exists(transferable_shader_cache_file_path)) { + const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir); + const auto transferable_shader_cache_folder_path = shader_cache_dir / "opengl" / "transferable"; + const auto transferable_shader_cache_file_path = + transferable_shader_cache_folder_path / fmt::format("{:016X}.bin", program_id); + + if (!Common::FS::Exists(transferable_shader_cache_file_path)) { QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), tr("A shader cache for this title does not exist.")); return; } + const auto qt_shader_cache_folder_path = + QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_folder_path)); + const auto qt_shader_cache_file_path = + QString::fromStdString(Common::FS::PathToUTF8String(transferable_shader_cache_file_path)); + // Windows supports opening a folder with selecting a specified file in explorer. On every other // OS we just open the transferable shader cache folder without preselecting the transferable // shader cache file for the selected game. #if defined(Q_OS_WIN) const QString explorer = QStringLiteral("explorer"); QStringList param; - if (!QFileInfo(transferable_shader_cache_file_path).isDir()) { + if (!QFileInfo(qt_shader_cache_file_path).isDir()) { param << QStringLiteral("/select,"); } - param << QDir::toNativeSeparators(transferable_shader_cache_file_path); + param << QDir::toNativeSeparators(qt_shader_cache_file_path); QProcess::startDetached(explorer, param); #else - QDesktopServices::openUrl(QUrl::fromLocalFile(transferable_shader_cache_folder_path)); + QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_folder_path)); #endif } @@ -1724,8 +1733,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT RemoveAddOnContent(program_id, entry_type); break; } - Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + - DIR_SEP + "game_list"); + void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list")); game_list->PopulateAsync(UISettings::values.game_dirs); } @@ -1814,21 +1823,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ } void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { - const QString shader_dir = - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)); - const QString transferable_shader_cache_folder_path = - shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); - const QString transferable_shader_cache_file_path = - transferable_shader_cache_folder_path + QDir::separator() + - QString::fromStdString(fmt::format("{:016X}.bin", program_id)); - - if (!QFile::exists(transferable_shader_cache_file_path)) { + const auto shader_cache_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir); + const auto transferable_shader_cache_file_path = + shader_cache_dir / "opengl" / "transferable" / fmt::format("{:016X}.bin", program_id); + + if (!Common::FS::Exists(transferable_shader_cache_file_path)) { QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), tr("A shader cache for this title does not exist.")); return; } - if (QFile::remove(transferable_shader_cache_file_path)) { + if (Common::FS::RemoveFile(transferable_shader_cache_file_path)) { QMessageBox::information(this, tr("Successfully Removed"), tr("Successfully removed the transferable shader cache.")); } else { @@ -1838,19 +1843,16 @@ void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { } void GMainWindow::RemoveCustomConfiguration(u64 program_id) { - const QString config_dir = - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir)); - const QString custom_config_file_path = - config_dir + QStringLiteral("custom") + QDir::separator() + - QString::fromStdString(fmt::format("{:016X}.ini", program_id)); + const auto custom_config_file_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / + "custom" / fmt::format("{:016X}.ini", program_id); - if (!QFile::exists(custom_config_file_path)) { + if (!Common::FS::Exists(custom_config_file_path)) { QMessageBox::warning(this, tr("Error Removing Custom Configuration"), tr("A custom configuration for this title does not exist.")); return; } - if (QFile::remove(custom_config_file_path)) { + if (Common::FS::RemoveFile(custom_config_file_path)) { QMessageBox::information(this, tr("Successfully Removed"), tr("Successfully removed the custom game configuration.")); } else { @@ -1887,8 +1889,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto path = fmt::format( - "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id); + const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir); + const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); + + const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); FileSys::VirtualFile romfs; @@ -1966,24 +1970,29 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, } void GMainWindow::OnGameListOpenDirectory(const QString& directory) { - QString path; + std::filesystem::path fs_path; if (directory == QStringLiteral("SDMC")) { - path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) + - "Nintendo/Contents/registered"); + fs_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "Nintendo/Contents/registered"; } else if (directory == QStringLiteral("UserNAND")) { - path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "user/Contents/registered"); + fs_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "user/Contents/registered"; } else if (directory == QStringLiteral("SysNAND")) { - path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + - "system/Contents/registered"); + fs_path = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/Contents/registered"; } else { - path = directory; + fs_path = directory.toStdString(); } - if (!QFileInfo::exists(path)) { - QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); + + const auto qt_path = QString::fromStdString(Common::FS::PathToUTF8String(fs_path)); + + if (!Common::FS::IsDir(fs_path)) { + QMessageBox::critical(this, tr("Error Opening %1").arg(qt_path), + tr("Folder does not exist!")); return; } - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + + QDesktopServices::openUrl(QUrl::fromLocalFile(qt_path)); } void GMainWindow::OnGameListAddDirectory() { @@ -2177,8 +2186,8 @@ void GMainWindow::OnMenuInstallToNAND() { : tr("%n file(s) failed to install\n", "", failed_files.size())); QMessageBox::information(this, tr("Install Results"), install_results); - Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + - DIR_SEP + "game_list"); + void(Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / + "game_list")); game_list->PopulateAsync(UISettings::values.game_dirs); ui.action_Install_File_NAND->setEnabled(true); } @@ -2694,7 +2703,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { void GMainWindow::OnOpenYuzuFolder() { QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir)))); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); } void GMainWindow::OnAbout() { @@ -2716,7 +2725,7 @@ void GMainWindow::OnCaptureScreenshot() { const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); const auto screenshot_path = - QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)); + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)); const auto date = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); QString filename = QStringLiteral("%1%2_%3.png") @@ -2745,23 +2754,26 @@ void GMainWindow::OnCaptureScreenshot() { // TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant void GMainWindow::MigrateConfigFiles() { - const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir); - const QDir config_dir = QDir(QString::fromStdString(config_dir_str)); + const auto config_dir_fs_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir); + const QDir config_dir = + QDir(QString::fromStdString(Common::FS::PathToUTF8String(config_dir_fs_path))); const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini"))); - Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str)); - for (QStringList::const_iterator it = config_dir_list.constBegin(); - it != config_dir_list.constEnd(); ++it) { + if (!Common::FS::CreateDirs(config_dir_fs_path / "custom")) { + LOG_ERROR(Frontend, "Failed to create new config file directory"); + } + + for (auto it = config_dir_list.constBegin(); it != config_dir_list.constEnd(); ++it) { const auto filename = it->toStdString(); if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) { continue; } - const auto origin = fmt::format("{}{}", config_dir_str, filename); - const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename); + const auto origin = config_dir_fs_path / filename; + const auto destination = config_dir_fs_path / "custom" / filename; LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); - if (!Common::FS::Rename(origin, destination)) { + if (!Common::FS::RenameFile(origin, destination)) { // Delete the old config file if one already exists in the new location. - Common::FS::Delete(origin); + void(Common::FS::RemoveFile(origin)); } } } @@ -2809,7 +2821,7 @@ void GMainWindow::UpdateStatusBar() { } else { emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); } - game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); + game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); @@ -2953,18 +2965,16 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { if (res == QMessageBox::Cancel) return; - Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + - "prod.keys_autogenerated"); - Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + - "console.keys_autogenerated"); - Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) + - "title.keys_autogenerated"); + const auto keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); + + void(Common::FS::RemoveFile(keys_dir / "prod.keys_autogenerated")); + void(Common::FS::RemoveFile(keys_dir / "console.keys_autogenerated")); + void(Common::FS::RemoveFile(keys_dir / "title.keys_autogenerated")); } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); if (keys.BaseDeriveNecessary()) { - Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( - Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)}; + Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; const auto function = [this, &keys, &pdm] { keys.PopulateFromPartitionData(pdm); @@ -3277,12 +3287,17 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); QCoreApplication::setApplicationName(QStringLiteral("yuzu")); +#ifdef _WIN32 + // Increases the maximum open file limit to 4096 + _setmaxstdio(4096); +#endif + #ifdef __APPLE__ // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". // But since we require the working directory to be the executable path for the location of // the user folder in the Qt Frontend, we need to cd into that working directory - const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + ".."; - chdir(bin_path.c_str()); + const auto bin_path = Common::FS::GetBundleDirectory() / ".."; + chdir(Common::FS::PathToUTF8String(bin_path).c_str()); #endif #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 @@ #endif #include <inih/cpp/INIReader.h> -#include "common/file_util.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/param_package.h" #include "common/settings.h" @@ -30,8 +32,8 @@ namespace FS = Common::FS; Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini"; - sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); + sdl2_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini"; + sdl2_config = std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc)); Reload(); } @@ -39,20 +41,23 @@ Config::Config() { Config::~Config() = default; bool Config::LoadINI(const std::string& default_contents, bool retry) { - const std::string& location = this->sdl2_config_loc; + const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc); if (sdl2_config->ParseError() < 0) { if (retry) { - LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); - FS::CreateFullPath(location); - FS::WriteStringToFile(true, location, default_contents); - sdl2_config = std::make_unique<INIReader>(location); // Reopen file + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", + config_loc_str); + + void(FS::CreateParentDir(sdl2_config_loc)); + void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents)); + + sdl2_config = std::make_unique<INIReader>(config_loc_str); return LoadINI(default_contents, false); } LOG_ERROR(Config, "Failed."); return false; } - LOG_INFO(Config, "Successfully loaded {}", location); + LOG_INFO(Config, "Successfully loaded {}", config_loc_str); return true; } @@ -327,18 +332,18 @@ void Config::ReadValues() { // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); - FS::GetUserPath( - FS::UserPath::NANDDir, - sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir))); - FS::GetUserPath( - FS::UserPath::SDMCDir, - sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir))); - FS::GetUserPath( - FS::UserPath::LoadDir, - sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir))); - FS::GetUserPath( - FS::UserPath::DumpDir, - sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); + FS::SetYuzuPath(FS::YuzuPath::NANDDir, + sdl2_config->Get("Data Storage", "nand_directory", + FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); + FS::SetYuzuPath(FS::YuzuPath::SDMCDir, + sdl2_config->Get("Data Storage", "sdmc_directory", + FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); + FS::SetYuzuPath(FS::YuzuPath::LoadDir, + sdl2_config->Get("Data Storage", "load_directory", + FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); + FS::SetYuzuPath(FS::YuzuPath::DumpDir, + sdl2_config->Get("Data Storage", "dump_directory", + FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); Settings::values.gamecard_inserted = sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); Settings::values.gamecard_current_game = @@ -361,10 +366,10 @@ void Config::ReadValues() { const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); if (custom_rtc_enabled) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0))); + Settings::values.custom_rtc = + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); } else { - Settings::values.custom_rtc.SetValue(std::nullopt); + Settings::values.custom_rtc = std::nullopt; } 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 @@ #pragma once +#include <filesystem> #include <memory> #include <string> @@ -11,7 +12,7 @@ class INIReader; class Config { std::unique_ptr<INIReader> sdl2_config; - std::string sdl2_config_loc; + std::filesystem::path sdl2_config_loc; bool LoadINI(const std::string& default_contents = "", bool retry = true); 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() { const auto results = Core::System::GetInstance().GetAndResetPerfStats(); const auto title = fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, - Common::g_scm_branch, Common::g_scm_desc, results.game_fps, + Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0); SDL_SetWindowTitle(render_window, title.c_str()); 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 @@ #include <fmt/ostream.h> -#include "common/common_paths.h" #include "common/detached_tasks.h" -#include "common/file_util.h" +#include "common/fs/fs.h" +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -82,9 +83,9 @@ static void InitializeLogging() { Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); - const std::string& log_dir = FS::GetUserPath(FS::UserPath::LogDir); - FS::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); + const auto& log_dir = FS::GetYuzuPath(FS::YuzuPath::LogDir); + void(FS::CreateDir(log_dir)); + Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir / LOG_FILE)); #ifdef _WIN32 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); #endif |