summaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/CMakeLists.txt14
-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.h453
-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/page_table.cpp1
-rw-r--r--src/common/page_table.h6
-rw-r--r--src/common/point.h57
-rw-r--r--src/common/settings.cpp16
-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
28 files changed, 3050 insertions, 1439 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 88644eeb6..7a4d9e354 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
@@ -147,6 +154,7 @@ add_library(common STATIC
param_package.cpp
param_package.h
parent_of_member.h
+ point.h
quaternion.h
ring_buffer.h
scm_rev.cpp
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..50e270c5b
--- /dev/null
+++ b/src/common/fs/file.h
@@ -0,0 +1,453 @@
+// 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 {
+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);
+
+ ~IOFile();
+
+ IOFile(const IOFile&) = delete;
+ IOFile& operator=(const IOFile&) = delete;
+
+ 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/page_table.cpp b/src/common/page_table.cpp
index 8fd8620fd..9fffd816f 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -14,6 +14,7 @@ void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_b
const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)};
pointers.resize(num_page_table_entries);
backing_addr.resize(num_page_table_entries);
+ current_address_space_width_in_bits = address_space_width_in_bits;
}
} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
index 61c5552e0..e92b66b2b 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -98,6 +98,10 @@ struct PageTable {
*/
void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits);
+ size_t GetAddressSpaceBits() const {
+ return current_address_space_width_in_bits;
+ }
+
/**
* Vector of memory pointers backing each page. An entry can only be non-null if the
* corresponding attribute element is of type `Memory`.
@@ -105,6 +109,8 @@ struct PageTable {
VirtualBuffer<PageInfo> pointers;
VirtualBuffer<u64> backing_addr;
+
+ size_t current_address_space_width_in_bits;
};
} // namespace Common
diff --git a/src/common/point.h b/src/common/point.h
new file mode 100644
index 000000000..c0a52ad8d
--- /dev/null
+++ b/src/common/point.h
@@ -0,0 +1,57 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+
+namespace Common {
+
+// Represents a point within a 2D space.
+template <typename T>
+struct Point {
+ static_assert(std::is_arithmetic_v<T>, "T must be an arithmetic type!");
+
+ T x{};
+ T y{};
+
+#define ARITHMETIC_OP(op, compound_op) \
+ friend constexpr Point operator op(const Point& lhs, const Point& rhs) noexcept { \
+ return { \
+ .x = static_cast<T>(lhs.x op rhs.x), \
+ .y = static_cast<T>(lhs.y op rhs.y), \
+ }; \
+ } \
+ friend constexpr Point operator op(const Point& lhs, T value) noexcept { \
+ return { \
+ .x = static_cast<T>(lhs.x op value), \
+ .y = static_cast<T>(lhs.y op value), \
+ }; \
+ } \
+ friend constexpr Point operator op(T value, const Point& rhs) noexcept { \
+ return { \
+ .x = static_cast<T>(value op rhs.x), \
+ .y = static_cast<T>(value op rhs.y), \
+ }; \
+ } \
+ friend constexpr Point& operator compound_op(Point& lhs, const Point& rhs) noexcept { \
+ lhs.x = static_cast<T>(lhs.x op rhs.x); \
+ lhs.y = static_cast<T>(lhs.y op rhs.y); \
+ return lhs; \
+ } \
+ friend constexpr Point& operator compound_op(Point& lhs, T value) noexcept { \
+ lhs.x = static_cast<T>(lhs.x op value); \
+ lhs.y = static_cast<T>(lhs.y op value); \
+ return lhs; \
+ }
+ ARITHMETIC_OP(+, +=)
+ ARITHMETIC_OP(-, -=)
+ ARITHMETIC_OP(*, *=)
+ ARITHMETIC_OP(/, /=)
+#undef ARITHMETIC_OP
+
+ friend constexpr bool operator==(const Point&, const Point&) = default;
+};
+
+} // namespace Common
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index e29cbf506..bcb4e4be1 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -5,7 +5,7 @@
#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));
@@ -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);
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