summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/common/CMakeLists.txt13
-rw-r--r--src/common/common_paths.h52
-rw-r--r--src/common/file_util.cpp1032
-rw-r--r--src/common/file_util.h298
-rw-r--r--src/common/fs/file.cpp392
-rw-r--r--src/common/fs/file.h450
-rw-r--r--src/common/fs/fs.cpp610
-rw-r--r--src/common/fs/fs.h582
-rw-r--r--src/common/fs/fs_paths.h27
-rw-r--r--src/common/fs/fs_types.h73
-rw-r--r--src/common/fs/fs_util.cpp13
-rw-r--r--src/common/fs/fs_util.h25
-rw-r--r--src/common/fs/path_util.cpp432
-rw-r--r--src/common/fs/path_util.h309
-rw-r--r--src/common/logging/backend.cpp27
-rw-r--r--src/common/logging/backend.h5
-rw-r--r--src/common/lz4_compression.cpp3
-rw-r--r--src/common/lz4_compression.h5
-rw-r--r--src/common/nvidia_flags.cpp24
-rw-r--r--src/common/parent_of_member.h3
-rw-r--r--src/common/settings.cpp25
-rw-r--r--src/common/settings.h10
-rw-r--r--src/common/string_util.cpp13
-rw-r--r--src/common/string_util.h2
-rw-r--r--src/common/tree.h11
-rw-r--r--src/common/zstd_compression.cpp2
-rw-r--r--src/common/zstd_compression.h5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp10
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp10
-rw-r--r--src/core/core.cpp9
-rw-r--r--src/core/crypto/key_manager.cpp139
-rw-r--r--src/core/crypto/key_manager.h6
-rw-r--r--src/core/file_sys/bis_factory.cpp4
-rw-r--r--src/core/file_sys/mode.h8
-rw-r--r--src/core/file_sys/partition_filesystem.cpp1
-rw-r--r--src/core/file_sys/patch_manager.cpp1
-rw-r--r--src/core/file_sys/registered_cache.cpp2
-rw-r--r--src/core/file_sys/vfs.cpp8
-rw-r--r--src/core/file_sys/vfs_libzip.cpp1
-rw-r--r--src/core/file_sys/vfs_real.cpp245
-rw-r--r--src/core/file_sys/vfs_real.h4
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/hle/ipc_helpers.h47
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp52
-rw-r--r--src/core/hle/kernel/hle_ipc.h130
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.cpp10
-rw-r--r--src/core/hle/kernel/k_client_port.cpp8
-rw-r--r--src/core/hle/kernel/k_client_port.h3
-rw-r--r--src/core/hle/kernel/k_port.cpp7
-rw-r--r--src/core/hle/kernel/k_server_port.h16
-rw-r--r--src/core/hle/kernel/k_server_session.cpp35
-rw-r--r--src/core/hle/kernel/k_server_session.h36
-rw-r--r--src/core/hle/kernel/k_session.cpp2
-rw-r--r--src/core/hle/kernel/k_session.h4
-rw-r--r--src/core/hle/kernel/k_slab_heap.h154
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h2
-rw-r--r--src/core/hle/kernel/process_capability.cpp9
-rw-r--r--src/core/hle/kernel/process_capability.h3
-rw-r--r--src/core/hle/kernel/service_thread.cpp14
-rw-r--r--src/core/hle/kernel/slab_helpers.h4
-rw-r--r--src/core/hle/service/acc/acc.cpp28
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp33
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp47
-rw-r--r--src/core/hle/service/am/applets/web_browser.h5
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp68
-rw-r--r--src/core/hle/service/fatal/fatal.cpp1
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp21
-rw-r--r--src/core/hle/service/mii/manager.cpp1
-rw-r--r--src/core/hle/service/ns/pl_u.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp1
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/sm/controller.cpp39
-rw-r--r--src/core/hle/service/sm/sm.cpp20
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp1
-rw-r--r--src/core/loader/elf.cpp1
-rw-r--r--src/core/loader/loader.cpp2
-rw-r--r--src/core/loader/nca.cpp1
-rw-r--r--src/core/loader/nro.cpp1
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/memory.cpp21
-rw-r--r--src/core/memory.h9
-rw-r--r--src/core/perf_stats.cpp31
-rw-r--r--src/core/perf_stats.h9
-rw-r--r--src/core/reporter.cpp23
-rw-r--r--src/core/telemetry_session.cpp47
-rw-r--r--src/input_common/sdl/sdl_impl.cpp54
-rw-r--r--src/input_common/udp/client.cpp23
-rw-r--r--src/tests/core/core_timing.cpp1
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h5
-rw-r--r--src/video_core/gpu.cpp5
-rw-r--r--src/video_core/gpu.h2
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp153
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h11
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp1
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp1
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp47
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp8
-rw-r--r--src/yuzu/applets/profile_select.cpp9
-rw-r--r--src/yuzu/applets/software_keyboard.cpp34
-rw-r--r--src/yuzu/applets/web_browser.cpp24
-rw-r--r--src/yuzu/configuration/config.cpp177
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp58
-rw-r--r--src/yuzu/configuration/configuration_shared.h34
-rw-r--r--src/yuzu/configuration/configure_audio.cpp10
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp86
-rw-r--r--src/yuzu/configuration/configure_cpu.h10
-rw-r--r--src/yuzu/configuration/configure_cpu.ui90
-rw-r--r--src/yuzu/configuration/configure_debug.cpp4
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp39
-rw-r--r--src/yuzu/configuration/configure_general.cpp12
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp42
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp46
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp3
-rw-r--r--src/yuzu/configuration/configure_per_game.ui11
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp8
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp13
-rw-r--r--src/yuzu/configuration/configure_system.cpp73
-rw-r--r--src/yuzu/configuration/configure_ui.cpp23
-rw-r--r--src/yuzu/configuration/input_profiles.cpp58
-rw-r--r--src/yuzu/debugger/controller.cpp1
-rw-r--r--src/yuzu/game_list_worker.cpp56
-rw-r--r--src/yuzu/game_list_worker.h2
-rw-r--r--src/yuzu/main.cpp243
-rw-r--r--src/yuzu_cmd/config.cpp53
-rw-r--r--src/yuzu_cmd/config.h3
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp11
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 &quot;Accurate&quot;.</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>
&lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
</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>
&lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
</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>
&lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
</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