summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
m---------externals/dynarmic0
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/concepts.h4
-rw-r--r--src/common/dynamic_library.cpp2
-rw-r--r--src/common/file_util.cpp68
-rw-r--r--src/common/file_util.h24
-rw-r--r--src/common/logging/backend.h2
-rw-r--r--src/common/telemetry.cpp4
-rw-r--r--src/common/telemetry.h4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp13
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp13
-rw-r--r--src/core/core.cpp14
-rw-r--r--src/core/core_timing.cpp12
-rw-r--r--src/core/core_timing_util.cpp1
-rw-r--r--src/core/core_timing_util.h1
-rw-r--r--src/core/cpu_manager.cpp29
-rw-r--r--src/core/crypto/aes_util.cpp2
-rw-r--r--src/core/crypto/key_manager.cpp439
-rw-r--r--src/core/crypto/key_manager.h8
-rw-r--r--src/core/crypto/partition_data_manager.cpp4
-rw-r--r--src/core/file_sys/bis_factory.cpp2
-rw-r--r--src/core/file_sys/registered_cache.cpp2
-rw-r--r--src/core/file_sys/vfs.cpp56
-rw-r--r--src/core/file_sys/vfs_libzip.cpp2
-rw-r--r--src/core/file_sys/vfs_real.cpp241
-rw-r--r--src/core/file_sys/vfs_real.h8
-rw-r--r--src/core/file_sys/xts_archive.cpp2
-rw-r--r--src/core/hle/service/acc/acc.cpp8
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp22
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp16
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp20
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp80
-rw-r--r--src/core/loader/loader.cpp2
-rw-r--r--src/core/perf_stats.cpp4
-rw-r--r--src/core/reporter.cpp9
-rw-r--r--src/core/settings.cpp4
-rw-r--r--src/core/settings.h6
-rw-r--r--src/core/telemetry_session.cpp14
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/video_core/fence_manager.h5
-rw-r--r--src/video_core/macro/macro_interpreter.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h1
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp57
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h10
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp7
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp14
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h28
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp37
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h31
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp26
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h10
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp4
-rw-r--r--src/video_core/renderer_vulkan/wrapper.h2
-rw-r--r--src/video_core/shader/async_shaders.cpp94
-rw-r--r--src/video_core/shader/async_shaders.h39
-rw-r--r--src/web_service/CMakeLists.txt1
-rw-r--r--src/web_service/telemetry_json.cpp6
-rw-r--r--src/web_service/telemetry_json.h30
-rw-r--r--src/web_service/verify_login.cpp2
-rw-r--r--src/web_service/web_backend.cpp56
-rw-r--r--src/web_service/web_backend.h20
-rw-r--r--src/web_service/web_result.h (renamed from src/common/web_result.h)4
-rw-r--r--src/yuzu/applets/profile_select.cpp2
-rw-r--r--src/yuzu/compatdb.cpp3
-rw-r--r--src/yuzu/configuration/config.cpp111
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp60
-rw-r--r--src/yuzu/configuration/configuration_shared.h13
-rw-r--r--src/yuzu/configuration/configure_audio.cpp13
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp17
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui52
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp37
-rw-r--r--src/yuzu/configuration/configure_general.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp32
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp22
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui2
-rw-r--r--src/yuzu/configuration/configure_hotkeys.cpp4
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp4
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp4
-rw-r--r--src/yuzu/configuration/configure_system.cpp18
-rw-r--r--src/yuzu/configuration/configure_ui.cpp12
-rw-r--r--src/yuzu/debugger/profiler.cpp3
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/game_list.h3
-rw-r--r--src/yuzu/game_list_worker.cpp26
-rw-r--r--src/yuzu/main.cpp146
-rw-r--r--src/yuzu/main.h5
-rw-r--r--src/yuzu/main.ui9
-rw-r--r--src/yuzu_cmd/config.cpp38
-rw-r--r--src/yuzu_cmd/yuzu.cpp6
-rw-r--r--src/yuzu_tester/config.cpp21
-rw-r--r--src/yuzu_tester/yuzu.cpp7
102 files changed, 1358 insertions, 1005 deletions
diff --git a/externals/dynarmic b/externals/dynarmic
Subproject 82417da7803e2cf18efc28a1cd3f3d0a4b6045a Subproject 0e1112b7df77ae55a62a51622940d5c8f9e8c84
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 78c3bfb3b..5d54516eb 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -172,7 +172,6 @@ add_library(common STATIC
172 virtual_buffer.h 172 virtual_buffer.h
173 wall_clock.cpp 173 wall_clock.cpp
174 wall_clock.h 174 wall_clock.h
175 web_result.h
176 zstd_compression.cpp 175 zstd_compression.cpp
177 zstd_compression.h 176 zstd_compression.h
178) 177)
diff --git a/src/common/concepts.h b/src/common/concepts.h
index 54252e778..5bef3ad67 100644
--- a/src/common/concepts.h
+++ b/src/common/concepts.h
@@ -4,10 +4,10 @@
4 4
5#pragma once 5#pragma once
6 6
7namespace Common {
8
9#include <type_traits> 7#include <type_traits>
10 8
9namespace Common {
10
11// Check if type is like an STL container 11// Check if type is like an STL container
12template <typename T> 12template <typename T>
13concept IsSTLContainer = requires(T t) { 13concept IsSTLContainer = requires(T t) {
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 7ab54e9e4..7f0a10521 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -21,7 +21,7 @@ namespace Common {
21DynamicLibrary::DynamicLibrary() = default; 21DynamicLibrary::DynamicLibrary() = default;
22 22
23DynamicLibrary::DynamicLibrary(const char* filename) { 23DynamicLibrary::DynamicLibrary(const char* filename) {
24 Open(filename); 24 void(Open(filename));
25} 25}
26 26
27DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept 27DynamicLibrary::DynamicLibrary(DynamicLibrary&& rhs) noexcept
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 4ede9f72c..16c3713e0 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -74,7 +74,7 @@
74// This namespace has various generic functions related to files and paths. 74// This namespace has various generic functions related to files and paths.
75// The code still needs a ton of cleanup. 75// The code still needs a ton of cleanup.
76// REMEMBER: strdup considered harmful! 76// REMEMBER: strdup considered harmful!
77namespace FileUtil { 77namespace Common::FS {
78 78
79// Remove any ending forward slashes from directory paths 79// Remove any ending forward slashes from directory paths
80// Modifies argument. 80// Modifies argument.
@@ -196,7 +196,7 @@ bool CreateFullPath(const std::string& fullPath) {
196 int panicCounter = 100; 196 int panicCounter = 100;
197 LOG_TRACE(Common_Filesystem, "path {}", fullPath); 197 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
198 198
199 if (FileUtil::Exists(fullPath)) { 199 if (Exists(fullPath)) {
200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath); 200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
201 return true; 201 return true;
202 } 202 }
@@ -212,7 +212,7 @@ bool CreateFullPath(const std::string& fullPath) {
212 212
213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") 213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
214 std::string const subPath(fullPath.substr(0, position + 1)); 214 std::string const subPath(fullPath.substr(0, position + 1));
215 if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) { 215 if (!IsDirectory(subPath) && !CreateDir(subPath)) {
216 LOG_ERROR(Common, "CreateFullPath: directory creation failed"); 216 LOG_ERROR(Common, "CreateFullPath: directory creation failed");
217 return false; 217 return false;
218 } 218 }
@@ -231,7 +231,7 @@ bool DeleteDir(const std::string& filename) {
231 LOG_TRACE(Common_Filesystem, "directory {}", filename); 231 LOG_TRACE(Common_Filesystem, "directory {}", filename);
232 232
233 // check if a directory 233 // check if a directory
234 if (!FileUtil::IsDirectory(filename)) { 234 if (!IsDirectory(filename)) {
235 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); 235 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
236 return false; 236 return false;
237 } 237 }
@@ -371,7 +371,7 @@ u64 GetSize(FILE* f) {
371bool CreateEmptyFile(const std::string& filename) { 371bool CreateEmptyFile(const std::string& filename) {
372 LOG_TRACE(Common_Filesystem, "{}", filename); 372 LOG_TRACE(Common_Filesystem, "{}", filename);
373 373
374 if (!FileUtil::IOFile(filename, "wb").IsOpen()) { 374 if (!IOFile(filename, "wb").IsOpen()) {
375 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); 375 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
376 return false; 376 return false;
377 } 377 }
@@ -488,29 +488,34 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
488 return false; 488 return false;
489 489
490 // Delete the outermost directory 490 // Delete the outermost directory
491 FileUtil::DeleteDir(directory); 491 DeleteDir(directory);
492 return true; 492 return true;
493} 493}
494 494
495void CopyDir(const std::string& source_path, const std::string& dest_path) { 495void CopyDir(const std::string& source_path, const std::string& dest_path) {
496#ifndef _WIN32 496#ifndef _WIN32
497 if (source_path == dest_path) 497 if (source_path == dest_path) {
498 return; 498 return;
499 if (!FileUtil::Exists(source_path)) 499 }
500 if (!Exists(source_path)) {
500 return; 501 return;
501 if (!FileUtil::Exists(dest_path)) 502 }
502 FileUtil::CreateFullPath(dest_path); 503 if (!Exists(dest_path)) {
504 CreateFullPath(dest_path);
505 }
503 506
504 DIR* dirp = opendir(source_path.c_str()); 507 DIR* dirp = opendir(source_path.c_str());
505 if (!dirp) 508 if (!dirp) {
506 return; 509 return;
510 }
507 511
508 while (struct dirent* result = readdir(dirp)) { 512 while (struct dirent* result = readdir(dirp)) {
509 const std::string virtualName(result->d_name); 513 const std::string virtualName(result->d_name);
510 // check for "." and ".." 514 // check for "." and ".."
511 if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || 515 if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
512 ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) 516 ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
513 continue; 517 continue;
518 }
514 519
515 std::string source, dest; 520 std::string source, dest;
516 source = source_path + virtualName; 521 source = source_path + virtualName;
@@ -518,11 +523,13 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
518 if (IsDirectory(source)) { 523 if (IsDirectory(source)) {
519 source += '/'; 524 source += '/';
520 dest += '/'; 525 dest += '/';
521 if (!FileUtil::Exists(dest)) 526 if (!Exists(dest)) {
522 FileUtil::CreateFullPath(dest); 527 CreateFullPath(dest);
528 }
523 CopyDir(source, dest); 529 CopyDir(source, dest);
524 } else if (!FileUtil::Exists(dest)) 530 } else if (!Exists(dest)) {
525 FileUtil::Copy(source, dest); 531 Copy(source, dest);
532 }
526 } 533 }
527 closedir(dirp); 534 closedir(dirp);
528#endif 535#endif
@@ -538,7 +545,7 @@ std::optional<std::string> GetCurrentDir() {
538 if (!dir) { 545 if (!dir) {
539#endif 546#endif
540 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); 547 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
541 return {}; 548 return std::nullopt;
542 } 549 }
543#ifdef _WIN32 550#ifdef _WIN32
544 std::string strDir = Common::UTF16ToUTF8(dir); 551 std::string strDir = Common::UTF16ToUTF8(dir);
@@ -546,7 +553,7 @@ std::optional<std::string> GetCurrentDir() {
546 std::string strDir = dir; 553 std::string strDir = dir;
547#endif 554#endif
548 free(dir); 555 free(dir);
549 return strDir; 556 return std::move(strDir);
550} 557}
551 558
552bool SetCurrentDir(const std::string& directory) { 559bool SetCurrentDir(const std::string& directory) {
@@ -668,7 +675,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
668 if (user_path.empty()) { 675 if (user_path.empty()) {
669#ifdef _WIN32 676#ifdef _WIN32
670 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; 677 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
671 if (!FileUtil::IsDirectory(user_path)) { 678 if (!IsDirectory(user_path)) {
672 user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; 679 user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
673 } else { 680 } else {
674 LOG_INFO(Common_Filesystem, "Using the local user directory"); 681 LOG_INFO(Common_Filesystem, "Using the local user directory");
@@ -677,7 +684,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
677 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); 684 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
678 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); 685 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
679#else 686#else
680 if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { 687 if (Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
681 user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; 688 user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
682 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); 689 paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
683 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); 690 paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
@@ -704,7 +711,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
704 } 711 }
705 712
706 if (!new_path.empty()) { 713 if (!new_path.empty()) {
707 if (!FileUtil::IsDirectory(new_path)) { 714 if (!IsDirectory(new_path)) {
708 LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path); 715 LOG_ERROR(Common_Filesystem, "Invalid path specified {}", new_path);
709 return paths[path]; 716 return paths[path];
710 } else { 717 } else {
@@ -902,10 +909,10 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
902 return std::string(RemoveTrailingSlash(path)); 909 return std::string(RemoveTrailingSlash(path));
903} 910}
904 911
905IOFile::IOFile() {} 912IOFile::IOFile() = default;
906 913
907IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { 914IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
908 Open(filename, openmode, flags); 915 void(Open(filename, openmode, flags));
909} 916}
910 917
911IOFile::~IOFile() { 918IOFile::~IOFile() {
@@ -946,17 +953,18 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
946} 953}
947 954
948bool IOFile::Close() { 955bool IOFile::Close() {
949 if (!IsOpen() || 0 != std::fclose(m_file)) 956 if (!IsOpen() || 0 != std::fclose(m_file)) {
950 return false; 957 return false;
958 }
951 959
952 m_file = nullptr; 960 m_file = nullptr;
953 return true; 961 return true;
954} 962}
955 963
956u64 IOFile::GetSize() const { 964u64 IOFile::GetSize() const {
957 if (IsOpen()) 965 if (IsOpen()) {
958 return FileUtil::GetSize(m_file); 966 return FS::GetSize(m_file);
959 967 }
960 return 0; 968 return 0;
961} 969}
962 970
@@ -965,9 +973,9 @@ bool IOFile::Seek(s64 off, int origin) const {
965} 973}
966 974
967u64 IOFile::Tell() const { 975u64 IOFile::Tell() const {
968 if (IsOpen()) 976 if (IsOpen()) {
969 return ftello(m_file); 977 return ftello(m_file);
970 978 }
971 return std::numeric_limits<u64>::max(); 979 return std::numeric_limits<u64>::max();
972} 980}
973 981
@@ -1016,4 +1024,4 @@ bool IOFile::Resize(u64 size) {
1016 ; 1024 ;
1017} 1025}
1018 1026
1019} // namespace FileUtil 1027} // namespace Common::FS
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 681b28137..8b587320f 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -19,7 +19,7 @@
19#include "common/string_util.h" 19#include "common/string_util.h"
20#endif 20#endif
21 21
22namespace FileUtil { 22namespace Common::FS {
23 23
24// User paths for GetUserPath 24// User paths for GetUserPath
25enum class UserPath { 25enum class UserPath {
@@ -204,6 +204,16 @@ enum class DirectorySeparator {
204 std::string_view path, 204 std::string_view path,
205 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); 205 DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
206 206
207// To deal with Windows being dumb at Unicode
208template <typename T>
209void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
210#ifdef _MSC_VER
211 fstream.open(Common::UTF8ToUTF16W(filename), openmode);
212#else
213 fstream.open(filename, openmode);
214#endif
215}
216
207// simple wrapper for cstdlib file functions to 217// simple wrapper for cstdlib file functions to
208// hopefully will make error checking easier 218// hopefully will make error checking easier
209// and make forgetting an fclose() harder 219// and make forgetting an fclose() harder
@@ -285,14 +295,4 @@ private:
285 std::FILE* m_file = nullptr; 295 std::FILE* m_file = nullptr;
286}; 296};
287 297
288} // namespace FileUtil 298} // namespace Common::FS
289
290// To deal with Windows being dumb at unicode:
291template <typename T>
292void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
293#ifdef _MSC_VER
294 fstream.open(Common::UTF8ToUTF16W(filename), openmode);
295#else
296 fstream.open(filename, openmode);
297#endif
298}
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index e5d702568..da1c2f185 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -94,7 +94,7 @@ public:
94 void Write(const Entry& entry) override; 94 void Write(const Entry& entry) override;
95 95
96private: 96private:
97 FileUtil::IOFile file; 97 Common::FS::IOFile file;
98 std::size_t bytes_written; 98 std::size_t bytes_written;
99}; 99};
100 100
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index 16d42facd..6241d08b3 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -12,7 +12,7 @@
12#include "common/x64/cpu_detect.h" 12#include "common/x64/cpu_detect.h"
13#endif 13#endif
14 14
15namespace Telemetry { 15namespace Common::Telemetry {
16 16
17void FieldCollection::Accept(VisitorInterface& visitor) const { 17void FieldCollection::Accept(VisitorInterface& visitor) const {
18 for (const auto& field : fields) { 18 for (const auto& field : fields) {
@@ -88,4 +88,4 @@ void AppendOSInfo(FieldCollection& fc) {
88#endif 88#endif
89} 89}
90 90
91} // namespace Telemetry 91} // namespace Common::Telemetry
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index 4aa299f9a..a50c5d1de 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -10,7 +10,7 @@
10#include <string> 10#include <string>
11#include "common/common_types.h" 11#include "common/common_types.h"
12 12
13namespace Telemetry { 13namespace Common::Telemetry {
14 14
15/// Field type, used for grouping fields together in the final submitted telemetry log 15/// Field type, used for grouping fields together in the final submitted telemetry log
16enum class FieldType : u8 { 16enum class FieldType : u8 {
@@ -196,4 +196,4 @@ void AppendCPUInfo(FieldCollection& fc);
196/// such as platform name, etc. 196/// such as platform name, etc.
197void AppendOSInfo(FieldCollection& fc); 197void AppendOSInfo(FieldCollection& fc);
198 198
199} // namespace Telemetry 199} // namespace Common::Telemetry
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index 443ca72eb..b5f28a86e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -143,7 +143,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
143 config.wall_clock_cntpct = uses_wall_clock; 143 config.wall_clock_cntpct = uses_wall_clock;
144 144
145 // Safe optimizations 145 // Safe optimizations
146 if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) { 146 if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
147 if (!Settings::values.cpuopt_page_tables) { 147 if (!Settings::values.cpuopt_page_tables) {
148 config.page_table = nullptr; 148 config.page_table = nullptr;
149 } 149 }
@@ -170,6 +170,17 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
170 } 170 }
171 } 171 }
172 172
173 // Unsafe optimizations
174 if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
175 config.unsafe_optimizations = true;
176 if (Settings::values.cpuopt_unsafe_unfuse_fma) {
177 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
178 }
179 if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
180 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
181 }
182 }
183
173 return std::make_unique<Dynarmic::A32::Jit>(config); 184 return std::make_unique<Dynarmic::A32::Jit>(config);
174} 185}
175 186
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index a63a04a25..ce9968724 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -195,7 +195,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
195 config.wall_clock_cntpct = uses_wall_clock; 195 config.wall_clock_cntpct = uses_wall_clock;
196 196
197 // Safe optimizations 197 // Safe optimizations
198 if (Settings::values.cpu_accuracy != Settings::CPUAccuracy::Accurate) { 198 if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::DebugMode) {
199 if (!Settings::values.cpuopt_page_tables) { 199 if (!Settings::values.cpuopt_page_tables) {
200 config.page_table = nullptr; 200 config.page_table = nullptr;
201 } 201 }
@@ -222,6 +222,17 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
222 } 222 }
223 } 223 }
224 224
225 // Unsafe optimizations
226 if (Settings::values.cpu_accuracy == Settings::CPUAccuracy::Unsafe) {
227 config.unsafe_optimizations = true;
228 if (Settings::values.cpuopt_unsafe_unfuse_fma) {
229 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
230 }
231 if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
232 config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
233 }
234 }
235
225 return std::make_shared<Dynarmic::A64::Jit>(config); 236 return std::make_shared<Dynarmic::A64::Jit>(config);
226} 237}
227 238
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 42277e2cd..c2c0eec0b 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -113,7 +113,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
113 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); 113 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
114 } 114 }
115 115
116 if (FileUtil::IsDirectory(path)) 116 if (Common::FS::IsDirectory(path))
117 return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); 117 return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
118 118
119 return vfs->OpenFile(path, FileSys::Mode::Read); 119 return vfs->OpenFile(path, FileSys::Mode::Read);
@@ -269,14 +269,14 @@ struct System::Impl {
269 // Log last frame performance stats if game was loded 269 // Log last frame performance stats if game was loded
270 if (perf_stats) { 270 if (perf_stats) {
271 const auto perf_results = GetAndResetPerfStats(); 271 const auto perf_results = GetAndResetPerfStats();
272 telemetry_session->AddField(Telemetry::FieldType::Performance, 272 constexpr auto performance = Common::Telemetry::FieldType::Performance;
273 "Shutdown_EmulationSpeed", 273
274 telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
274 perf_results.emulation_speed * 100.0); 275 perf_results.emulation_speed * 100.0);
275 telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate", 276 telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps);
276 perf_results.game_fps); 277 telemetry_session->AddField(performance, "Shutdown_Frametime",
277 telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
278 perf_results.frametime * 1000.0); 278 perf_results.frametime * 1000.0);
279 telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", 279 telemetry_session->AddField(performance, "Mean_Frametime_MS",
280 perf_stats->GetMeanFrametime()); 280 perf_stats->GetMeanFrametime());
281 } 281 }
282 282
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 71af26ec5..e6c8461a5 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -7,14 +7,14 @@
7#include <string> 7#include <string>
8#include <tuple> 8#include <tuple>
9 9
10#include "common/assert.h"
11#include "common/microprofile.h" 10#include "common/microprofile.h"
12#include "core/core_timing.h" 11#include "core/core_timing.h"
13#include "core/core_timing_util.h" 12#include "core/core_timing_util.h"
13#include "core/hardware_properties.h"
14 14
15namespace Core::Timing { 15namespace Core::Timing {
16 16
17constexpr u64 MAX_SLICE_LENGTH = 4000; 17constexpr s64 MAX_SLICE_LENGTH = 4000;
18 18
19std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) { 19std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
20 return std::make_shared<EventType>(std::move(callback), std::move(name)); 20 return std::make_shared<EventType>(std::move(callback), std::move(name));
@@ -37,10 +37,8 @@ struct CoreTiming::Event {
37 } 37 }
38}; 38};
39 39
40CoreTiming::CoreTiming() { 40CoreTiming::CoreTiming()
41 clock = 41 : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
42 Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ);
43}
44 42
45CoreTiming::~CoreTiming() = default; 43CoreTiming::~CoreTiming() = default;
46 44
@@ -136,7 +134,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
136 134
137void CoreTiming::AddTicks(u64 ticks) { 135void CoreTiming::AddTicks(u64 ticks) {
138 this->ticks += ticks; 136 this->ticks += ticks;
139 downcount -= ticks; 137 downcount -= static_cast<s64>(ticks);
140} 138}
141 139
142void CoreTiming::Idle() { 140void CoreTiming::Idle() {
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index aefc63663..8ce8e602e 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -8,6 +8,7 @@
8#include <limits> 8#include <limits>
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/uint128.h" 10#include "common/uint128.h"
11#include "core/hardware_properties.h"
11 12
12namespace Core::Timing { 13namespace Core::Timing {
13 14
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index 2ed979e14..e4a046bf9 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -6,7 +6,6 @@
6 6
7#include <chrono> 7#include <chrono>
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/hardware_properties.h"
10 9
11namespace Core::Timing { 10namespace Core::Timing {
12 11
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 358943429..ef0bae556 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -41,9 +41,9 @@ void CpuManager::Shutdown() {
41 running_mode = false; 41 running_mode = false;
42 Pause(false); 42 Pause(false);
43 if (is_multicore) { 43 if (is_multicore) {
44 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 44 for (auto& data : core_data) {
45 core_data[core].host_thread->join(); 45 data.host_thread->join();
46 core_data[core].host_thread.reset(); 46 data.host_thread.reset();
47 } 47 }
48 } else { 48 } else {
49 core_data[0].host_thread->join(); 49 core_data[0].host_thread->join();
@@ -166,25 +166,23 @@ void CpuManager::MultiCorePause(bool paused) {
166 bool all_not_barrier = false; 166 bool all_not_barrier = false;
167 while (!all_not_barrier) { 167 while (!all_not_barrier) {
168 all_not_barrier = true; 168 all_not_barrier = true;
169 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 169 for (const auto& data : core_data) {
170 all_not_barrier &= 170 all_not_barrier &= !data.is_running.load() && data.initialized.load();
171 !core_data[core].is_running.load() && core_data[core].initialized.load();
172 } 171 }
173 } 172 }
174 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 173 for (auto& data : core_data) {
175 core_data[core].enter_barrier->Set(); 174 data.enter_barrier->Set();
176 } 175 }
177 if (paused_state.load()) { 176 if (paused_state.load()) {
178 bool all_barrier = false; 177 bool all_barrier = false;
179 while (!all_barrier) { 178 while (!all_barrier) {
180 all_barrier = true; 179 all_barrier = true;
181 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 180 for (const auto& data : core_data) {
182 all_barrier &= 181 all_barrier &= data.is_paused.load() && data.initialized.load();
183 core_data[core].is_paused.load() && core_data[core].initialized.load();
184 } 182 }
185 } 183 }
186 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 184 for (auto& data : core_data) {
187 core_data[core].exit_barrier->Set(); 185 data.exit_barrier->Set();
188 } 186 }
189 } 187 }
190 } else { 188 } else {
@@ -192,9 +190,8 @@ void CpuManager::MultiCorePause(bool paused) {
192 bool all_barrier = false; 190 bool all_barrier = false;
193 while (!all_barrier) { 191 while (!all_barrier) {
194 all_barrier = true; 192 all_barrier = true;
195 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { 193 for (const auto& data : core_data) {
196 all_barrier &= 194 all_barrier &= data.is_paused.load() && data.initialized.load();
197 core_data[core].is_paused.load() && core_data[core].initialized.load();
198 } 195 }
199 } 196 }
200 /// Don't release the barrier 197 /// Don't release the barrier
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 330996b24..6a9734812 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -116,7 +116,7 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8*
116 116
117 for (std::size_t i = 0; i < size; i += sector_size) { 117 for (std::size_t i = 0; i < size; i += sector_size) {
118 SetIV(CalculateNintendoTweak(sector_id++)); 118 SetIV(CalculateNintendoTweak(sector_id++));
119 Transcode<u8, u8>(src + i, sector_size, dest + i, op); 119 Transcode(src + i, sector_size, dest + i, op);
120 } 120 }
121} 121}
122 122
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index c09f7ad41..dc591c730 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -36,6 +36,7 @@
36#include "core/settings.h" 36#include "core/settings.h"
37 37
38namespace Core::Crypto { 38namespace Core::Crypto {
39namespace {
39 40
40constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 41constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
41constexpr u64 FULL_TICKET_SIZE = 0x400; 42constexpr u64 FULL_TICKET_SIZE = 0x400;
@@ -49,7 +50,72 @@ constexpr std::array eticket_source_hashes{
49}; 50};
50// clang-format on 51// clang-format on
51 52
52const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ 53constexpr std::array<std::pair<std::string_view, KeyIndex<S128KeyType>>, 30> s128_file_id{{
54 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
55 {"eticket_rsa_kek_source",
56 {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
57 {"eticket_rsa_kekek_source",
58 {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
59 {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
60 {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
61 {"rsa_oaep_kek_generation_source",
62 {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
63 {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
64 {"aes_kek_generation_source",
65 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
66 {"aes_key_generation_source",
67 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
68 {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
69 {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
70 {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
71 {"key_area_key_application_source",
72 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
73 static_cast<u64>(KeyAreaKeyType::Application)}},
74 {"key_area_key_ocean_source",
75 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
76 static_cast<u64>(KeyAreaKeyType::Ocean)}},
77 {"key_area_key_system_source",
78 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
79 static_cast<u64>(KeyAreaKeyType::System)}},
80 {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
81 {"keyblob_mac_key_source",
82 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}},
83 {"tsec_key", {S128KeyType::TSEC, 0, 0}},
84 {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
85 {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
86 {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
87 {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
88 {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
89 {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
90 {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
91 {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
92 {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
93 {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
94 {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
95 {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
96}};
97
98auto Find128ByName(std::string_view name) {
99 return std::find_if(s128_file_id.begin(), s128_file_id.end(),
100 [&name](const auto& pair) { return pair.first == name; });
101}
102
103constexpr std::array<std::pair<std::string_view, KeyIndex<S256KeyType>>, 6> s256_file_id{{
104 {"header_key", {S256KeyType::Header, 0, 0}},
105 {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
106 {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
107 {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
108 {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
109 {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
110}};
111
112auto Find256ByName(std::string_view name) {
113 return std::find_if(s256_file_id.begin(), s256_file_id.end(),
114 [&name](const auto& pair) { return pair.first == name; });
115}
116
117using KeyArray = std::array<std::pair<std::pair<S128KeyType, u64>, std::string_view>, 7>;
118constexpr KeyArray KEYS_VARIABLE_LENGTH{{
53 {{S128KeyType::Master, 0}, "master_key_"}, 119 {{S128KeyType::Master, 0}, "master_key_"},
54 {{S128KeyType::Package1, 0}, "package1_key_"}, 120 {{S128KeyType::Package1, 0}, "package1_key_"},
55 {{S128KeyType::Package2, 0}, "package2_key_"}, 121 {{S128KeyType::Package2, 0}, "package2_key_"},
@@ -57,14 +123,13 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
57 {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, 123 {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
58 {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, 124 {{S128KeyType::Keyblob, 0}, "keyblob_key_"},
59 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, 125 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
60}; 126}};
61 127
62namespace {
63template <std::size_t Size> 128template <std::size_t Size>
64bool IsAllZeroArray(const std::array<u8, Size>& array) { 129bool IsAllZeroArray(const std::array<u8, Size>& array) {
65 return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); 130 return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
66} 131}
67} // namespace 132} // Anonymous namespace
68 133
69u64 GetSignatureTypeDataSize(SignatureType type) { 134u64 GetSignatureTypeDataSize(SignatureType type) {
70 switch (type) { 135 switch (type) {
@@ -96,13 +161,13 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
96} 161}
97 162
98SignatureType Ticket::GetSignatureType() const { 163SignatureType Ticket::GetSignatureType() const {
99 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { 164 if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
100 return ticket->sig_type; 165 return ticket->sig_type;
101 } 166 }
102 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { 167 if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
103 return ticket->sig_type; 168 return ticket->sig_type;
104 } 169 }
105 if (auto ticket = std::get_if<ECDSATicket>(&data)) { 170 if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
106 return ticket->sig_type; 171 return ticket->sig_type;
107 } 172 }
108 173
@@ -110,13 +175,13 @@ SignatureType Ticket::GetSignatureType() const {
110} 175}
111 176
112TicketData& Ticket::GetData() { 177TicketData& Ticket::GetData() {
113 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { 178 if (auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
114 return ticket->data; 179 return ticket->data;
115 } 180 }
116 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { 181 if (auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
117 return ticket->data; 182 return ticket->data;
118 } 183 }
119 if (auto ticket = std::get_if<ECDSATicket>(&data)) { 184 if (auto* ticket = std::get_if<ECDSATicket>(&data)) {
120 return ticket->data; 185 return ticket->data;
121 } 186 }
122 187
@@ -124,13 +189,13 @@ TicketData& Ticket::GetData() {
124} 189}
125 190
126const TicketData& Ticket::GetData() const { 191const TicketData& Ticket::GetData() const {
127 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { 192 if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) {
128 return ticket->data; 193 return ticket->data;
129 } 194 }
130 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { 195 if (const auto* ticket = std::get_if<RSA2048Ticket>(&data)) {
131 return ticket->data; 196 return ticket->data;
132 } 197 }
133 if (auto ticket = std::get_if<ECDSATicket>(&data)) { 198 if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
134 return ticket->data; 199 return ticket->data;
135 } 200 }
136 201
@@ -233,8 +298,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
233} 298}
234 299
235RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { 300RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
236 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) 301 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) {
237 return {}; 302 return {};
303 }
238 304
239 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); 305 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
240 306
@@ -261,27 +327,30 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
261} 327}
262 328
263std::optional<Key128> DeriveSDSeed() { 329std::optional<Key128> DeriveSDSeed() {
264 const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 330 const Common::FS::IOFile save_43(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
265 "/system/save/8000000000000043", 331 "/system/save/8000000000000043",
266 "rb+"); 332 "rb+");
267 if (!save_43.IsOpen()) 333 if (!save_43.IsOpen()) {
268 return {}; 334 return std::nullopt;
335 }
269 336
270 const FileUtil::IOFile sd_private( 337 const Common::FS::IOFile sd_private(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
271 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); 338 "/Nintendo/Contents/private",
272 if (!sd_private.IsOpen()) 339 "rb+");
273 return {}; 340 if (!sd_private.IsOpen()) {
341 return std::nullopt;
342 }
274 343
275 std::array<u8, 0x10> private_seed{}; 344 std::array<u8, 0x10> private_seed{};
276 if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { 345 if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
277 return {}; 346 return std::nullopt;
278 } 347 }
279 348
280 std::array<u8, 0x10> buffer{}; 349 std::array<u8, 0x10> buffer{};
281 std::size_t offset = 0; 350 std::size_t offset = 0;
282 for (; offset + 0x10 < save_43.GetSize(); ++offset) { 351 for (; offset + 0x10 < save_43.GetSize(); ++offset) {
283 if (!save_43.Seek(offset, SEEK_SET)) { 352 if (!save_43.Seek(offset, SEEK_SET)) {
284 return {}; 353 return std::nullopt;
285 } 354 }
286 355
287 save_43.ReadBytes(buffer.data(), buffer.size()); 356 save_43.ReadBytes(buffer.data(), buffer.size());
@@ -291,23 +360,26 @@ std::optional<Key128> DeriveSDSeed() {
291 } 360 }
292 361
293 if (!save_43.Seek(offset + 0x10, SEEK_SET)) { 362 if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
294 return {}; 363 return std::nullopt;
295 } 364 }
296 365
297 Key128 seed{}; 366 Key128 seed{};
298 if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { 367 if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
299 return {}; 368 return std::nullopt;
300 } 369 }
301 return seed; 370 return seed;
302} 371}
303 372
304Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) { 373Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
305 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) 374 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek))) {
306 return Loader::ResultStatus::ErrorMissingSDKEKSource; 375 return Loader::ResultStatus::ErrorMissingSDKEKSource;
307 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) 376 }
377 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration))) {
308 return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; 378 return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
309 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) 379 }
380 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) {
310 return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; 381 return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
382 }
311 383
312 const auto sd_kek_source = 384 const auto sd_kek_source =
313 keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)); 385 keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
@@ -320,14 +392,17 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
320 GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); 392 GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
321 keys.SetKey(S128KeyType::SDKek, sd_kek); 393 keys.SetKey(S128KeyType::SDKek, sd_kek);
322 394
323 if (!keys.HasKey(S128KeyType::SDSeed)) 395 if (!keys.HasKey(S128KeyType::SDSeed)) {
324 return Loader::ResultStatus::ErrorMissingSDSeed; 396 return Loader::ResultStatus::ErrorMissingSDSeed;
397 }
325 const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); 398 const auto sd_seed = keys.GetKey(S128KeyType::SDSeed);
326 399
327 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) 400 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) {
328 return Loader::ResultStatus::ErrorMissingSDSaveKeySource; 401 return Loader::ResultStatus::ErrorMissingSDSaveKeySource;
329 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) 402 }
403 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) {
330 return Loader::ResultStatus::ErrorMissingSDNCAKeySource; 404 return Loader::ResultStatus::ErrorMissingSDNCAKeySource;
405 }
331 406
332 std::array<Key256, 2> sd_key_sources{ 407 std::array<Key256, 2> sd_key_sources{
333 keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)), 408 keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)),
@@ -336,8 +411,9 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
336 411
337 // Combine sources and seed 412 // Combine sources and seed
338 for (auto& source : sd_key_sources) { 413 for (auto& source : sd_key_sources) {
339 for (std::size_t i = 0; i < source.size(); ++i) 414 for (std::size_t i = 0; i < source.size(); ++i) {
340 source[i] ^= sd_seed[i & 0xF]; 415 source[i] ^= sd_seed[i & 0xF];
416 }
341 } 417 }
342 418
343 AESCipher<Key128> cipher(sd_kek, Mode::ECB); 419 AESCipher<Key128> cipher(sd_kek, Mode::ECB);
@@ -355,9 +431,10 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
355 return Loader::ResultStatus::Success; 431 return Loader::ResultStatus::Success;
356} 432}
357 433
358std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { 434std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
359 if (!ticket_save.IsOpen()) 435 if (!ticket_save.IsOpen()) {
360 return {}; 436 return {};
437 }
361 438
362 std::vector<u8> buffer(ticket_save.GetSize()); 439 std::vector<u8> buffer(ticket_save.GetSize());
363 if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { 440 if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
@@ -417,7 +494,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
417 offset = i + 1; 494 offset = i + 1;
418 break; 495 break;
419 } else if (data[i] != 0x0) { 496 } else if (data[i] != 0x0) {
420 return {}; 497 return std::nullopt;
421 } 498 }
422 } 499 }
423 500
@@ -427,16 +504,18 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
427std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, 504std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
428 const RSAKeyPair<2048>& key) { 505 const RSAKeyPair<2048>& key) {
429 const auto issuer = ticket.GetData().issuer; 506 const auto issuer = ticket.GetData().issuer;
430 if (IsAllZeroArray(issuer)) 507 if (IsAllZeroArray(issuer)) {
431 return {}; 508 return std::nullopt;
509 }
432 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { 510 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
433 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); 511 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
434 } 512 }
435 513
436 Key128 rights_id = ticket.GetData().rights_id; 514 Key128 rights_id = ticket.GetData().rights_id;
437 515
438 if (rights_id == Key128{}) 516 if (rights_id == Key128{}) {
439 return {}; 517 return std::nullopt;
518 }
440 519
441 if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), 520 if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
442 ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { 521 ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
@@ -468,15 +547,17 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
468 std::array<u8, 0xDF> m_2; 547 std::array<u8, 0xDF> m_2;
469 std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); 548 std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
470 549
471 if (m_0 != 0) 550 if (m_0 != 0) {
472 return {}; 551 return std::nullopt;
552 }
473 553
474 m_1 = m_1 ^ MGF1<0x20>(m_2); 554 m_1 = m_1 ^ MGF1<0x20>(m_2);
475 m_2 = m_2 ^ MGF1<0xDF>(m_1); 555 m_2 = m_2 ^ MGF1<0xDF>(m_1);
476 556
477 const auto offset = FindTicketOffset(m_2); 557 const auto offset = FindTicketOffset(m_2);
478 if (!offset) 558 if (!offset) {
479 return {}; 559 return std::nullopt;
560 }
480 ASSERT(*offset > 0); 561 ASSERT(*offset > 0);
481 562
482 Key128 key_temp{}; 563 Key128 key_temp{};
@@ -487,8 +568,8 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
487 568
488KeyManager::KeyManager() { 569KeyManager::KeyManager() {
489 // Initialize keys 570 // Initialize keys
490 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); 571 const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
491 const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); 572 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
492 if (Settings::values.use_dev_keys) { 573 if (Settings::values.use_dev_keys) {
493 dev_mode = true; 574 dev_mode = true;
494 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); 575 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
@@ -506,34 +587,39 @@ KeyManager::KeyManager() {
506} 587}
507 588
508static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { 589static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
509 if (base.size() < begin + length) 590 if (base.size() < begin + length) {
510 return false; 591 return false;
592 }
511 return std::all_of(base.begin() + begin, base.begin() + begin + length, 593 return std::all_of(base.begin() + begin, base.begin() + begin + length,
512 [](u8 c) { return std::isxdigit(c); }); 594 [](u8 c) { return std::isxdigit(c); });
513} 595}
514 596
515void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { 597void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
516 std::ifstream file; 598 std::ifstream file;
517 OpenFStream(file, filename, std::ios_base::in); 599 Common::FS::OpenFStream(file, filename, std::ios_base::in);
518 if (!file.is_open()) 600 if (!file.is_open()) {
519 return; 601 return;
602 }
520 603
521 std::string line; 604 std::string line;
522 while (std::getline(file, line)) { 605 while (std::getline(file, line)) {
523 std::vector<std::string> out; 606 std::vector<std::string> out;
524 std::stringstream stream(line); 607 std::stringstream stream(line);
525 std::string item; 608 std::string item;
526 while (std::getline(stream, item, '=')) 609 while (std::getline(stream, item, '=')) {
527 out.push_back(std::move(item)); 610 out.push_back(std::move(item));
611 }
528 612
529 if (out.size() != 2) 613 if (out.size() != 2) {
530 continue; 614 continue;
615 }
531 616
532 out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); 617 out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
533 out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); 618 out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
534 619
535 if (out[0].compare(0, 1, "#") == 0) 620 if (out[0].compare(0, 1, "#") == 0) {
536 continue; 621 continue;
622 }
537 623
538 if (is_title_keys) { 624 if (is_title_keys) {
539 auto rights_id_raw = Common::HexStringToArray<16>(out[0]); 625 auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
@@ -543,24 +629,26 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
543 s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; 629 s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
544 } else { 630 } else {
545 out[0] = Common::ToLower(out[0]); 631 out[0] = Common::ToLower(out[0]);
546 if (s128_file_id.find(out[0]) != s128_file_id.end()) { 632 if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) {
547 const auto index = s128_file_id.at(out[0]); 633 const auto& index = iter128->second;
548 Key128 key = Common::HexStringToArray<16>(out[1]); 634 const Key128 key = Common::HexStringToArray<16>(out[1]);
549 s128_keys[{index.type, index.field1, index.field2}] = key; 635 s128_keys[{index.type, index.field1, index.field2}] = key;
550 } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { 636 } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) {
551 const auto index = s256_file_id.at(out[0]); 637 const auto& index = iter256->second;
552 Key256 key = Common::HexStringToArray<32>(out[1]); 638 const Key256 key = Common::HexStringToArray<32>(out[1]);
553 s256_keys[{index.type, index.field1, index.field2}] = key; 639 s256_keys[{index.type, index.field1, index.field2}] = key;
554 } else if (out[0].compare(0, 8, "keyblob_") == 0 && 640 } else if (out[0].compare(0, 8, "keyblob_") == 0 &&
555 out[0].compare(0, 9, "keyblob_k") != 0) { 641 out[0].compare(0, 9, "keyblob_k") != 0) {
556 if (!ValidCryptoRevisionString(out[0], 8, 2)) 642 if (!ValidCryptoRevisionString(out[0], 8, 2)) {
557 continue; 643 continue;
644 }
558 645
559 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 646 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
560 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 647 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
561 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 648 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
562 if (!ValidCryptoRevisionString(out[0], 18, 2)) 649 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
563 continue; 650 continue;
651 }
564 652
565 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 653 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
566 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 654 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
@@ -568,8 +656,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
568 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 656 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
569 } else { 657 } else {
570 for (const auto& kv : KEYS_VARIABLE_LENGTH) { 658 for (const auto& kv : KEYS_VARIABLE_LENGTH) {
571 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) 659 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) {
572 continue; 660 continue;
661 }
573 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 662 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
574 const auto index = 663 const auto index =
575 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 664 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
@@ -604,10 +693,11 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
604 693
605void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, 694void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
606 const std::string& filename, bool title) { 695 const std::string& filename, bool title) {
607 if (FileUtil::Exists(dir1 + DIR_SEP + filename)) 696 if (Common::FS::Exists(dir1 + DIR_SEP + filename)) {
608 LoadFromFile(dir1 + DIR_SEP + filename, title); 697 LoadFromFile(dir1 + DIR_SEP + filename, title);
609 else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) 698 } else if (Common::FS::Exists(dir2 + DIR_SEP + filename)) {
610 LoadFromFile(dir2 + DIR_SEP + filename, title); 699 LoadFromFile(dir2 + DIR_SEP + filename, title);
700 }
611} 701}
612 702
613bool KeyManager::BaseDeriveNecessary() const { 703bool KeyManager::BaseDeriveNecessary() const {
@@ -615,8 +705,9 @@ bool KeyManager::BaseDeriveNecessary() const {
615 return !HasKey(key_type, index1, index2); 705 return !HasKey(key_type, index1, index2);
616 }; 706 };
617 707
618 if (check_key_existence(S256KeyType::Header)) 708 if (check_key_existence(S256KeyType::Header)) {
619 return true; 709 return true;
710 }
620 711
621 for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) { 712 for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
622 if (check_key_existence(S128KeyType::Master, i) || 713 if (check_key_existence(S128KeyType::Master, i) ||
@@ -641,14 +732,16 @@ bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
641} 732}
642 733
643Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { 734Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
644 if (!HasKey(id, field1, field2)) 735 if (!HasKey(id, field1, field2)) {
645 return {}; 736 return {};
737 }
646 return s128_keys.at({id, field1, field2}); 738 return s128_keys.at({id, field1, field2});
647} 739}
648 740
649Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { 741Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
650 if (!HasKey(id, field1, field2)) 742 if (!HasKey(id, field1, field2)) {
651 return {}; 743 return {};
744 }
652 return s256_keys.at({id, field1, field2}); 745 return s256_keys.at({id, field1, field2});
653} 746}
654 747
@@ -670,7 +763,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
670template <size_t Size> 763template <size_t Size>
671void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, 764void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
672 const std::array<u8, Size>& key) { 765 const std::array<u8, Size>& key) {
673 const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); 766 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
674 std::string filename = "title.keys_autogenerated"; 767 std::string filename = "title.keys_autogenerated";
675 if (category == KeyCategory::Standard) { 768 if (category == KeyCategory::Standard) {
676 filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; 769 filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
@@ -679,9 +772,9 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
679 } 772 }
680 773
681 const auto path = yuzu_keys_dir + DIR_SEP + filename; 774 const auto path = yuzu_keys_dir + DIR_SEP + filename;
682 const auto add_info_text = !FileUtil::Exists(path); 775 const auto add_info_text = !Common::FS::Exists(path);
683 FileUtil::CreateFullPath(path); 776 Common::FS::CreateFullPath(path);
684 FileUtil::IOFile file{path, "a"}; 777 Common::FS::IOFile file{path, "a"};
685 if (!file.IsOpen()) { 778 if (!file.IsOpen()) {
686 return; 779 return;
687 } 780 }
@@ -714,8 +807,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
714 } 807 }
715 808
716 const auto iter2 = std::find_if( 809 const auto iter2 = std::find_if(
717 s128_file_id.begin(), s128_file_id.end(), 810 s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) {
718 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
719 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == 811 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
720 std::tie(id, field1, field2); 812 std::tie(id, field1, field2);
721 }); 813 });
@@ -725,9 +817,11 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
725 817
726 // Variable cases 818 // Variable cases
727 if (id == S128KeyType::KeyArea) { 819 if (id == S128KeyType::KeyArea) {
728 static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}", 820 static constexpr std::array<const char*, 3> kak_names = {
729 "key_area_key_ocean_{:02X}", 821 "key_area_key_application_{:02X}",
730 "key_area_key_system_{:02X}"}; 822 "key_area_key_ocean_{:02X}",
823 "key_area_key_system_{:02X}",
824 };
731 WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key); 825 WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
732 } else if (id == S128KeyType::Master) { 826 } else if (id == S128KeyType::Master) {
733 WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key); 827 WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
@@ -753,8 +847,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
753 return; 847 return;
754 } 848 }
755 const auto iter = std::find_if( 849 const auto iter = std::find_if(
756 s256_file_id.begin(), s256_file_id.end(), 850 s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) {
757 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
758 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == 851 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
759 std::tie(id, field1, field2); 852 std::tie(id, field1, field2);
760 }); 853 });
@@ -765,29 +858,31 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
765} 858}
766 859
767bool KeyManager::KeyFileExists(bool title) { 860bool KeyManager::KeyFileExists(bool title) {
768 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); 861 const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
769 const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); 862 const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
770 if (title) { 863 if (title) {
771 return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || 864 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
772 FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); 865 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
773 } 866 }
774 867
775 if (Settings::values.use_dev_keys) { 868 if (Settings::values.use_dev_keys) {
776 return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || 869 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
777 FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); 870 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
778 } 871 }
779 872
780 return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || 873 return Common::FS::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
781 FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); 874 Common::FS::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
782} 875}
783 876
784void KeyManager::DeriveSDSeedLazy() { 877void KeyManager::DeriveSDSeedLazy() {
785 if (HasKey(S128KeyType::SDSeed)) 878 if (HasKey(S128KeyType::SDSeed)) {
786 return; 879 return;
880 }
787 881
788 const auto res = DeriveSDSeed(); 882 const auto res = DeriveSDSeed();
789 if (res) 883 if (res) {
790 SetKey(S128KeyType::SDSeed, *res); 884 SetKey(S128KeyType::SDSeed, *res);
885 }
791} 886}
792 887
793static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { 888static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
@@ -799,11 +894,13 @@ static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
799} 894}
800 895
801void KeyManager::DeriveBase() { 896void KeyManager::DeriveBase() {
802 if (!BaseDeriveNecessary()) 897 if (!BaseDeriveNecessary()) {
803 return; 898 return;
899 }
804 900
805 if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) 901 if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) {
806 return; 902 return;
903 }
807 904
808 const auto has_bis = [this](u64 id) { 905 const auto has_bis = [this](u64 id) {
809 return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) && 906 return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
@@ -820,10 +917,11 @@ void KeyManager::DeriveBase() {
820 static_cast<u64>(BISKeyType::Tweak)); 917 static_cast<u64>(BISKeyType::Tweak));
821 }; 918 };
822 919
823 if (has_bis(2) && !has_bis(3)) 920 if (has_bis(2) && !has_bis(3)) {
824 copy_bis(2, 3); 921 copy_bis(2, 3);
825 else if (has_bis(3) && !has_bis(2)) 922 } else if (has_bis(3) && !has_bis(2)) {
826 copy_bis(3, 2); 923 copy_bis(3, 2);
924 }
827 925
828 std::bitset<32> revisions(0xFFFFFFFF); 926 std::bitset<32> revisions(0xFFFFFFFF);
829 for (size_t i = 0; i < revisions.size(); ++i) { 927 for (size_t i = 0; i < revisions.size(); ++i) {
@@ -833,15 +931,17 @@ void KeyManager::DeriveBase() {
833 } 931 }
834 } 932 }
835 933
836 if (!revisions.any()) 934 if (!revisions.any()) {
837 return; 935 return;
936 }
838 937
839 const auto sbk = GetKey(S128KeyType::SecureBoot); 938 const auto sbk = GetKey(S128KeyType::SecureBoot);
840 const auto tsec = GetKey(S128KeyType::TSEC); 939 const auto tsec = GetKey(S128KeyType::TSEC);
841 940
842 for (size_t i = 0; i < revisions.size(); ++i) { 941 for (size_t i = 0; i < revisions.size(); ++i) {
843 if (!revisions[i]) 942 if (!revisions[i]) {
844 continue; 943 continue;
944 }
845 945
846 // Derive keyblob key 946 // Derive keyblob key
847 const auto key = DeriveKeyblobKey( 947 const auto key = DeriveKeyblobKey(
@@ -850,16 +950,18 @@ void KeyManager::DeriveBase() {
850 SetKey(S128KeyType::Keyblob, key, i); 950 SetKey(S128KeyType::Keyblob, key, i);
851 951
852 // Derive keyblob MAC key 952 // Derive keyblob MAC key
853 if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) 953 if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) {
854 continue; 954 continue;
955 }
855 956
856 const auto mac_key = DeriveKeyblobMACKey( 957 const auto mac_key = DeriveKeyblobMACKey(
857 key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))); 958 key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
858 SetKey(S128KeyType::KeyblobMAC, mac_key, i); 959 SetKey(S128KeyType::KeyblobMAC, mac_key, i);
859 960
860 Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key); 961 Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
861 if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) 962 if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) {
862 continue; 963 continue;
964 }
863 965
864 // Decrypt keyblob 966 // Decrypt keyblob
865 if (keyblobs[i] == std::array<u8, 0x90>{}) { 967 if (keyblobs[i] == std::array<u8, 0x90>{}) {
@@ -883,16 +985,19 @@ void KeyManager::DeriveBase() {
883 985
884 revisions.set(); 986 revisions.set();
885 for (size_t i = 0; i < revisions.size(); ++i) { 987 for (size_t i = 0; i < revisions.size(); ++i) {
886 if (!HasKey(S128KeyType::Master, i)) 988 if (!HasKey(S128KeyType::Master, i)) {
887 revisions.reset(i); 989 revisions.reset(i);
990 }
888 } 991 }
889 992
890 if (!revisions.any()) 993 if (!revisions.any()) {
891 return; 994 return;
995 }
892 996
893 for (size_t i = 0; i < revisions.size(); ++i) { 997 for (size_t i = 0; i < revisions.size(); ++i) {
894 if (!revisions[i]) 998 if (!revisions[i]) {
895 continue; 999 continue;
1000 }
896 1001
897 // Derive general purpose keys 1002 // Derive general purpose keys
898 DeriveGeneralPurposeKeys(i); 1003 DeriveGeneralPurposeKeys(i);
@@ -922,16 +1027,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
922 const auto es = Core::System::GetInstance().GetContentProvider().GetEntry( 1027 const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
923 0x0100000000000033, FileSys::ContentRecordType::Program); 1028 0x0100000000000033, FileSys::ContentRecordType::Program);
924 1029
925 if (es == nullptr) 1030 if (es == nullptr) {
926 return; 1031 return;
1032 }
927 1033
928 const auto exefs = es->GetExeFS(); 1034 const auto exefs = es->GetExeFS();
929 if (exefs == nullptr) 1035 if (exefs == nullptr) {
930 return; 1036 return;
1037 }
931 1038
932 const auto main = exefs->GetFile("main"); 1039 const auto main = exefs->GetFile("main");
933 if (main == nullptr) 1040 if (main == nullptr) {
934 return; 1041 return;
1042 }
935 1043
936 const auto bytes = main->ReadAllBytes(); 1044 const auto bytes = main->ReadAllBytes();
937 1045
@@ -941,16 +1049,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
941 const auto seed3 = data.GetRSAKekSeed3(); 1049 const auto seed3 = data.GetRSAKekSeed3();
942 const auto mask0 = data.GetRSAKekMask0(); 1050 const auto mask0 = data.GetRSAKekMask0();
943 1051
944 if (eticket_kek != Key128{}) 1052 if (eticket_kek != Key128{}) {
945 SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek)); 1053 SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
1054 }
946 if (eticket_kekek != Key128{}) { 1055 if (eticket_kekek != Key128{}) {
947 SetKey(S128KeyType::Source, eticket_kekek, 1056 SetKey(S128KeyType::Source, eticket_kekek,
948 static_cast<size_t>(SourceKeyType::ETicketKekek)); 1057 static_cast<size_t>(SourceKeyType::ETicketKekek));
949 } 1058 }
950 if (seed3 != Key128{}) 1059 if (seed3 != Key128{}) {
951 SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); 1060 SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
952 if (mask0 != Key128{}) 1061 }
1062 if (mask0 != Key128{}) {
953 SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0)); 1063 SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
1064 }
954 if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || 1065 if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
955 mask0 == Key128{}) { 1066 mask0 == Key128{}) {
956 return; 1067 return;
@@ -976,8 +1087,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
976 AESCipher<Key128> es_kek(temp_kekek, Mode::ECB); 1087 AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
977 es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt); 1088 es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
978 1089
979 if (eticket_final == Key128{}) 1090 if (eticket_final == Key128{}) {
980 return; 1091 return;
1092 }
981 1093
982 SetKey(S128KeyType::ETicketRSAKek, eticket_final); 1094 SetKey(S128KeyType::ETicketRSAKek, eticket_final);
983 1095
@@ -992,18 +1104,20 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
992void KeyManager::PopulateTickets() { 1104void KeyManager::PopulateTickets() {
993 const auto rsa_key = GetETicketRSAKey(); 1105 const auto rsa_key = GetETicketRSAKey();
994 1106
995 if (rsa_key == RSAKeyPair<2048>{}) 1107 if (rsa_key == RSAKeyPair<2048>{}) {
996 return; 1108 return;
1109 }
997 1110
998 if (!common_tickets.empty() && !personal_tickets.empty()) 1111 if (!common_tickets.empty() && !personal_tickets.empty()) {
999 return; 1112 return;
1113 }
1000 1114
1001 const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 1115 const Common::FS::IOFile save1(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
1002 "/system/save/80000000000000e1", 1116 "/system/save/80000000000000e1",
1003 "rb+"); 1117 "rb+");
1004 const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 1118 const Common::FS::IOFile save2(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
1005 "/system/save/80000000000000e2", 1119 "/system/save/80000000000000e2",
1006 "rb+"); 1120 "rb+");
1007 1121
1008 const auto blob2 = GetTicketblob(save2); 1122 const auto blob2 = GetTicketblob(save2);
1009 auto res = GetTicketblob(save1); 1123 auto res = GetTicketblob(save1);
@@ -1013,8 +1127,10 @@ void KeyManager::PopulateTickets() {
1013 for (std::size_t i = 0; i < res.size(); ++i) { 1127 for (std::size_t i = 0; i < res.size(); ++i) {
1014 const auto common = i < idx; 1128 const auto common = i < idx;
1015 const auto pair = ParseTicket(res[i], rsa_key); 1129 const auto pair = ParseTicket(res[i], rsa_key);
1016 if (!pair) 1130 if (!pair) {
1017 continue; 1131 continue;
1132 }
1133
1018 const auto& [rid, key] = *pair; 1134 const auto& [rid, key] = *pair;
1019 u128 rights_id; 1135 u128 rights_id;
1020 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1136 std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1043,27 +1159,33 @@ void KeyManager::SynthesizeTickets() {
1043} 1159}
1044 1160
1045void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { 1161void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
1046 if (key == Key128{}) 1162 if (key == Key128{}) {
1047 return; 1163 return;
1164 }
1048 SetKey(id, key, field1, field2); 1165 SetKey(id, key, field1, field2);
1049} 1166}
1050 1167
1051void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { 1168void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
1052 if (key == Key256{}) 1169 if (key == Key256{}) {
1053 return; 1170 return;
1171 }
1172
1054 SetKey(id, key, field1, field2); 1173 SetKey(id, key, field1, field2);
1055} 1174}
1056 1175
1057void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { 1176void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
1058 if (!BaseDeriveNecessary()) 1177 if (!BaseDeriveNecessary()) {
1059 return; 1178 return;
1179 }
1060 1180
1061 if (!data.HasBoot0()) 1181 if (!data.HasBoot0()) {
1062 return; 1182 return;
1183 }
1063 1184
1064 for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { 1185 for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
1065 if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) 1186 if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) {
1066 continue; 1187 continue;
1188 }
1067 encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); 1189 encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
1068 WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), 1190 WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
1069 encrypted_keyblobs[i]); 1191 encrypted_keyblobs[i]);
@@ -1085,8 +1207,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
1085 static_cast<u64>(SourceKeyType::Keyblob), i); 1207 static_cast<u64>(SourceKeyType::Keyblob), i);
1086 } 1208 }
1087 1209
1088 if (data.HasFuses()) 1210 if (data.HasFuses()) {
1089 SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); 1211 SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
1212 }
1090 1213
1091 DeriveBase(); 1214 DeriveBase();
1092 1215
@@ -1100,8 +1223,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
1100 1223
1101 const auto masters = data.GetTZMasterKeys(latest_master); 1224 const auto masters = data.GetTZMasterKeys(latest_master);
1102 for (size_t i = 0; i < masters.size(); ++i) { 1225 for (size_t i = 0; i < masters.size(); ++i) {
1103 if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) 1226 if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) {
1104 SetKey(S128KeyType::Master, masters[i], i); 1227 SetKey(S128KeyType::Master, masters[i], i);
1228 }
1105 } 1229 }
1106 1230
1107 DeriveBase(); 1231 DeriveBase();
@@ -1111,8 +1235,9 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
1111 1235
1112 std::array<Key128, 0x20> package2_keys{}; 1236 std::array<Key128, 0x20> package2_keys{};
1113 for (size_t i = 0; i < package2_keys.size(); ++i) { 1237 for (size_t i = 0; i < package2_keys.size(); ++i) {
1114 if (HasKey(S128KeyType::Package2, i)) 1238 if (HasKey(S128KeyType::Package2, i)) {
1115 package2_keys[i] = GetKey(S128KeyType::Package2, i); 1239 package2_keys[i] = GetKey(S128KeyType::Package2, i);
1240 }
1116 } 1241 }
1117 data.DecryptPackage2(package2_keys, Package2Type::NormalMain); 1242 data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
1118 1243
@@ -1150,12 +1275,15 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
1150 1275
1151bool KeyManager::AddTicketCommon(Ticket raw) { 1276bool KeyManager::AddTicketCommon(Ticket raw) {
1152 const auto rsa_key = GetETicketRSAKey(); 1277 const auto rsa_key = GetETicketRSAKey();
1153 if (rsa_key == RSAKeyPair<2048>{}) 1278 if (rsa_key == RSAKeyPair<2048>{}) {
1154 return false; 1279 return false;
1280 }
1155 1281
1156 const auto pair = ParseTicket(raw, rsa_key); 1282 const auto pair = ParseTicket(raw, rsa_key);
1157 if (!pair) 1283 if (!pair) {
1158 return false; 1284 return false;
1285 }
1286
1159 const auto& [rid, key] = *pair; 1287 const auto& [rid, key] = *pair;
1160 u128 rights_id; 1288 u128 rights_id;
1161 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1289 std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1166,12 +1294,15 @@ bool KeyManager::AddTicketCommon(Ticket raw) {
1166 1294
1167bool KeyManager::AddTicketPersonalized(Ticket raw) { 1295bool KeyManager::AddTicketPersonalized(Ticket raw) {
1168 const auto rsa_key = GetETicketRSAKey(); 1296 const auto rsa_key = GetETicketRSAKey();
1169 if (rsa_key == RSAKeyPair<2048>{}) 1297 if (rsa_key == RSAKeyPair<2048>{}) {
1170 return false; 1298 return false;
1299 }
1171 1300
1172 const auto pair = ParseTicket(raw, rsa_key); 1301 const auto pair = ParseTicket(raw, rsa_key);
1173 if (!pair) 1302 if (!pair) {
1174 return false; 1303 return false;
1304 }
1305
1175 const auto& [rid, key] = *pair; 1306 const auto& [rid, key] = *pair;
1176 u128 rights_id; 1307 u128 rights_id;
1177 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1308 std::memcpy(rights_id.data(), rid.data(), rid.size());
@@ -1179,58 +1310,4 @@ bool KeyManager::AddTicketPersonalized(Ticket raw) {
1179 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); 1310 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1180 return true; 1311 return true;
1181} 1312}
1182
1183const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
1184 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
1185 {"eticket_rsa_kek_source",
1186 {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
1187 {"eticket_rsa_kekek_source",
1188 {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
1189 {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
1190 {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
1191 {"rsa_oaep_kek_generation_source",
1192 {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
1193 {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
1194 {"aes_kek_generation_source",
1195 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
1196 {"aes_key_generation_source",
1197 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
1198 {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
1199 {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
1200 {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
1201 {"key_area_key_application_source",
1202 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
1203 static_cast<u64>(KeyAreaKeyType::Application)}},
1204 {"key_area_key_ocean_source",
1205 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
1206 static_cast<u64>(KeyAreaKeyType::Ocean)}},
1207 {"key_area_key_system_source",
1208 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
1209 static_cast<u64>(KeyAreaKeyType::System)}},
1210 {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
1211 {"keyblob_mac_key_source",
1212 {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC), 0}},
1213 {"tsec_key", {S128KeyType::TSEC, 0, 0}},
1214 {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
1215 {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
1216 {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
1217 {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
1218 {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
1219 {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
1220 {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
1221 {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
1222 {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
1223 {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
1224 {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
1225 {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
1226};
1227
1228const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
1229 {"header_key", {S256KeyType::Header, 0, 0}},
1230 {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
1231 {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
1232 {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
1233 {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
1234 {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
1235};
1236} // namespace Core::Crypto 1313} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 9269a73f2..321b75323 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -10,14 +10,13 @@
10#include <string> 10#include <string>
11 11
12#include <variant> 12#include <variant>
13#include <boost/container/flat_map.hpp>
14#include <fmt/format.h> 13#include <fmt/format.h>
15#include "common/common_funcs.h" 14#include "common/common_funcs.h"
16#include "common/common_types.h" 15#include "common/common_types.h"
17#include "core/crypto/partition_data_manager.h" 16#include "core/crypto/partition_data_manager.h"
18#include "core/file_sys/vfs_types.h" 17#include "core/file_sys/vfs_types.h"
19 18
20namespace FileUtil { 19namespace Common::FS {
21class IOFile; 20class IOFile;
22} 21}
23 22
@@ -293,9 +292,6 @@ private:
293 292
294 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); 293 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
295 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); 294 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
296
297 static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
298 static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
299}; 295};
300 296
301Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); 297Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
@@ -308,7 +304,7 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
308std::optional<Key128> DeriveSDSeed(); 304std::optional<Key128> DeriveSDSeed();
309Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); 305Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
310 306
311std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save); 307std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save);
312 308
313// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority 309// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
314// (offset 0x140-0x144 is zero) 310// (offset 0x140-0x144 is zero)
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 3e96f7516..46136d04a 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -367,8 +367,8 @@ static bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header
367 Package2Header temp = header; 367 Package2Header temp = header;
368 AESCipher<Key128> cipher(key, Mode::CTR); 368 AESCipher<Key128> cipher(key, Mode::CTR);
369 cipher.SetIV(header.header_ctr); 369 cipher.SetIV(header.header_ctr);
370 cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr, 370 cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - sizeof(Package2Header::signature),
371 Op::Decrypt); 371 &temp.header_ctr, Op::Decrypt);
372 if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { 372 if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
373 header = temp; 373 header = temp;
374 return true; 374 return true;
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 285277ef8..9ffda2e14 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -86,7 +86,7 @@ VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
86 auto& keys = Core::Crypto::KeyManager::Instance(); 86 auto& keys = Core::Crypto::KeyManager::Instance();
87 Core::Crypto::PartitionDataManager pdm{ 87 Core::Crypto::PartitionDataManager pdm{
88 Core::System::GetInstance().GetFilesystem()->OpenDirectory( 88 Core::System::GetInstance().GetFilesystem()->OpenDirectory(
89 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)}; 89 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
90 keys.PopulateFromPartitionData(pdm); 90 keys.PopulateFromPartitionData(pdm);
91 91
92 switch (id) { 92 switch (id) {
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index f831487dd..e42b677f7 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -728,7 +728,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
728 LOG_WARNING(Loader, "Overwriting existing NCA..."); 728 LOG_WARNING(Loader, "Overwriting existing NCA...");
729 VirtualDir c_dir; 729 VirtualDir c_dir;
730 { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } 730 { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
731 c_dir->DeleteFile(FileUtil::GetFilename(path)); 731 c_dir->DeleteFile(Common::FS::GetFilename(path));
732 } 732 }
733 733
734 auto out = dir->CreateFileRelative(path); 734 auto out = dir->CreateFileRelative(path);
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index e33327ef0..a4c3f67c4 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -30,7 +30,7 @@ bool VfsFilesystem::IsWritable() const {
30} 30}
31 31
32VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const { 32VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
33 const auto path = FileUtil::SanitizePath(path_); 33 const auto path = Common::FS::SanitizePath(path_);
34 if (root->GetFileRelative(path) != nullptr) 34 if (root->GetFileRelative(path) != nullptr)
35 return VfsEntryType::File; 35 return VfsEntryType::File;
36 if (root->GetDirectoryRelative(path) != nullptr) 36 if (root->GetDirectoryRelative(path) != nullptr)
@@ -40,22 +40,22 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
40} 40}
41 41
42VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) { 42VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
43 const auto path = FileUtil::SanitizePath(path_); 43 const auto path = Common::FS::SanitizePath(path_);
44 return root->GetFileRelative(path); 44 return root->GetFileRelative(path);
45} 45}
46 46
47VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 47VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
48 const auto path = FileUtil::SanitizePath(path_); 48 const auto path = Common::FS::SanitizePath(path_);
49 return root->CreateFileRelative(path); 49 return root->CreateFileRelative(path);
50} 50}
51 51
52VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { 52VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
53 const auto old_path = FileUtil::SanitizePath(old_path_); 53 const auto old_path = Common::FS::SanitizePath(old_path_);
54 const auto new_path = FileUtil::SanitizePath(new_path_); 54 const auto new_path = Common::FS::SanitizePath(new_path_);
55 55
56 // VfsDirectory impls are only required to implement copy across the current directory. 56 // VfsDirectory impls are only required to implement copy across the current directory.
57 if (FileUtil::GetParentPath(old_path) == FileUtil::GetParentPath(new_path)) { 57 if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
58 if (!root->Copy(FileUtil::GetFilename(old_path), FileUtil::GetFilename(new_path))) 58 if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
59 return nullptr; 59 return nullptr;
60 return OpenFile(new_path, Mode::ReadWrite); 60 return OpenFile(new_path, Mode::ReadWrite);
61 } 61 }
@@ -76,8 +76,8 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
76} 76}
77 77
78VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) { 78VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
79 const auto sanitized_old_path = FileUtil::SanitizePath(old_path); 79 const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
80 const auto sanitized_new_path = FileUtil::SanitizePath(new_path); 80 const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
81 81
82 // Again, non-default impls are highly encouraged to provide a more optimized version of this. 82 // Again, non-default impls are highly encouraged to provide a more optimized version of this.
83 auto out = CopyFile(sanitized_old_path, sanitized_new_path); 83 auto out = CopyFile(sanitized_old_path, sanitized_new_path);
@@ -89,26 +89,26 @@ VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view
89} 89}
90 90
91bool VfsFilesystem::DeleteFile(std::string_view path_) { 91bool VfsFilesystem::DeleteFile(std::string_view path_) {
92 const auto path = FileUtil::SanitizePath(path_); 92 const auto path = Common::FS::SanitizePath(path_);
93 auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); 93 auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
94 if (parent == nullptr) 94 if (parent == nullptr)
95 return false; 95 return false;
96 return parent->DeleteFile(FileUtil::GetFilename(path)); 96 return parent->DeleteFile(Common::FS::GetFilename(path));
97} 97}
98 98
99VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { 99VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
100 const auto path = FileUtil::SanitizePath(path_); 100 const auto path = Common::FS::SanitizePath(path_);
101 return root->GetDirectoryRelative(path); 101 return root->GetDirectoryRelative(path);
102} 102}
103 103
104VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { 104VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
105 const auto path = FileUtil::SanitizePath(path_); 105 const auto path = Common::FS::SanitizePath(path_);
106 return root->CreateDirectoryRelative(path); 106 return root->CreateDirectoryRelative(path);
107} 107}
108 108
109VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) { 109VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
110 const auto old_path = FileUtil::SanitizePath(old_path_); 110 const auto old_path = Common::FS::SanitizePath(old_path_);
111 const auto new_path = FileUtil::SanitizePath(new_path_); 111 const auto new_path = Common::FS::SanitizePath(new_path_);
112 112
113 // Non-default impls are highly encouraged to provide a more optimized version of this. 113 // Non-default impls are highly encouraged to provide a more optimized version of this.
114 auto old_dir = OpenDirectory(old_path, Mode::Read); 114 auto old_dir = OpenDirectory(old_path, Mode::Read);
@@ -139,8 +139,8 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
139} 139}
140 140
141VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) { 141VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
142 const auto sanitized_old_path = FileUtil::SanitizePath(old_path); 142 const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
143 const auto sanitized_new_path = FileUtil::SanitizePath(new_path); 143 const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
144 144
145 // Non-default impls are highly encouraged to provide a more optimized version of this. 145 // Non-default impls are highly encouraged to provide a more optimized version of this.
146 auto out = CopyDirectory(sanitized_old_path, sanitized_new_path); 146 auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
@@ -152,17 +152,17 @@ VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_v
152} 152}
153 153
154bool VfsFilesystem::DeleteDirectory(std::string_view path_) { 154bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
155 const auto path = FileUtil::SanitizePath(path_); 155 const auto path = Common::FS::SanitizePath(path_);
156 auto parent = OpenDirectory(FileUtil::GetParentPath(path), Mode::Write); 156 auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
157 if (parent == nullptr) 157 if (parent == nullptr)
158 return false; 158 return false;
159 return parent->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path)); 159 return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
160} 160}
161 161
162VfsFile::~VfsFile() = default; 162VfsFile::~VfsFile() = default;
163 163
164std::string VfsFile::GetExtension() const { 164std::string VfsFile::GetExtension() const {
165 return std::string(FileUtil::GetExtensionFromFilename(GetName())); 165 return std::string(Common::FS::GetExtensionFromFilename(GetName()));
166} 166}
167 167
168VfsDirectory::~VfsDirectory() = default; 168VfsDirectory::~VfsDirectory() = default;
@@ -203,7 +203,7 @@ std::string VfsFile::GetFullPath() const {
203} 203}
204 204
205std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const { 205std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
206 auto vec = FileUtil::SplitPathComponents(path); 206 auto vec = Common::FS::SplitPathComponents(path);
207 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), 207 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
208 vec.end()); 208 vec.end());
209 if (vec.empty()) { 209 if (vec.empty()) {
@@ -239,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
239} 239}
240 240
241std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const { 241std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
242 auto vec = FileUtil::SplitPathComponents(path); 242 auto vec = Common::FS::SplitPathComponents(path);
243 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), 243 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
244 vec.end()); 244 vec.end());
245 if (vec.empty()) { 245 if (vec.empty()) {
@@ -301,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const {
301} 301}
302 302
303std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) { 303std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
304 auto vec = FileUtil::SplitPathComponents(path); 304 auto vec = Common::FS::SplitPathComponents(path);
305 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), 305 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
306 vec.end()); 306 vec.end());
307 if (vec.empty()) { 307 if (vec.empty()) {
@@ -320,7 +320,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
320 } 320 }
321 } 321 }
322 322
323 return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path)); 323 return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
324} 324}
325 325
326std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) { 326std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
@@ -332,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
332} 332}
333 333
334std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) { 334std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
335 auto vec = FileUtil::SplitPathComponents(path); 335 auto vec = Common::FS::SplitPathComponents(path);
336 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), 336 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
337 vec.end()); 337 vec.end());
338 if (vec.empty()) { 338 if (vec.empty()) {
@@ -351,7 +351,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
351 } 351 }
352 } 352 }
353 353
354 return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path)); 354 return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
355} 355}
356 356
357std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { 357std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index d69952940..429d7bc8b 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -49,7 +49,7 @@ VirtualDir ExtractZIP(VirtualFile file) {
49 if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size())) 49 if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size()))
50 return nullptr; 50 return nullptr;
51 51
52 const auto parts = FileUtil::SplitPathComponents(stat.name); 52 const auto parts = Common::FS::SplitPathComponents(stat.name);
53 const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back()); 53 const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
54 54
55 std::shared_ptr<VectorVfsDirectory> dtrv = out; 55 std::shared_ptr<VectorVfsDirectory> dtrv = out;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 0db0091f6..488687ba9 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -14,6 +14,8 @@
14 14
15namespace FileSys { 15namespace FileSys {
16 16
17namespace FS = Common::FS;
18
17static std::string ModeFlagsToString(Mode mode) { 19static std::string ModeFlagsToString(Mode mode) {
18 std::string mode_str; 20 std::string mode_str;
19 21
@@ -57,79 +59,82 @@ bool RealVfsFilesystem::IsWritable() const {
57} 59}
58 60
59VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { 61VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
60 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 62 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
61 if (!FileUtil::Exists(path)) 63 if (!FS::Exists(path)) {
62 return VfsEntryType::None; 64 return VfsEntryType::None;
63 if (FileUtil::IsDirectory(path)) 65 }
66 if (FS::IsDirectory(path)) {
64 return VfsEntryType::Directory; 67 return VfsEntryType::Directory;
68 }
65 69
66 return VfsEntryType::File; 70 return VfsEntryType::File;
67} 71}
68 72
69VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { 73VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
70 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 74 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
71 if (cache.find(path) != cache.end()) { 75
72 auto weak = cache[path]; 76 if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) {
77 const auto& weak = weak_iter->second;
78
73 if (!weak.expired()) { 79 if (!weak.expired()) {
74 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); 80 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms));
75 } 81 }
76 } 82 }
77 83
78 if (!FileUtil::Exists(path) && True(perms & Mode::WriteAppend)) { 84 if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
79 FileUtil::CreateEmptyFile(path); 85 FS::CreateEmptyFile(path);
80 } 86 }
81 87
82 auto backing = std::make_shared<FileUtil::IOFile>(path, ModeFlagsToString(perms).c_str()); 88 auto backing = std::make_shared<FS::IOFile>(path, ModeFlagsToString(perms).c_str());
83 cache[path] = backing; 89 cache.insert_or_assign(path, backing);
84 90
85 // Cannot use make_shared as RealVfsFile constructor is private 91 // Cannot use make_shared as RealVfsFile constructor is private
86 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); 92 return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms));
87} 93}
88 94
89VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 95VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
90 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 96 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
91 const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); 97 const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
92 if (!FileUtil::Exists(path)) { 98 if (!FS::Exists(path)) {
93 FileUtil::CreateFullPath(path_fwd); 99 FS::CreateFullPath(path_fwd);
94 if (!FileUtil::CreateEmptyFile(path)) 100 if (!FS::CreateEmptyFile(path)) {
95 return nullptr; 101 return nullptr;
102 }
96 } 103 }
97 return OpenFile(path, perms); 104 return OpenFile(path, perms);
98} 105}
99 106
100VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { 107VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
101 const auto old_path = 108 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
102 FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); 109 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
103 const auto new_path =
104 FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
105 110
106 if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || 111 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
107 FileUtil::IsDirectory(old_path) || !FileUtil::Copy(old_path, new_path)) 112 !FS::Copy(old_path, new_path)) {
108 return nullptr; 113 return nullptr;
114 }
109 return OpenFile(new_path, Mode::ReadWrite); 115 return OpenFile(new_path, Mode::ReadWrite);
110} 116}
111 117
112VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { 118VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
113 const auto old_path = 119 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
114 FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); 120 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
115 const auto new_path = 121 const auto cached_file_iter = cache.find(old_path);
116 FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
117 122
118 if (cache.find(old_path) != cache.end()) { 123 if (cached_file_iter != cache.cend()) {
119 auto file = cache[old_path].lock(); 124 auto file = cached_file_iter->second.lock();
120 125
121 if (!cache[old_path].expired()) { 126 if (!cached_file_iter->second.expired()) {
122 file->Close(); 127 file->Close();
123 } 128 }
124 129
125 if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || 130 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
126 FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) { 131 !FS::Rename(old_path, new_path)) {
127 return nullptr; 132 return nullptr;
128 } 133 }
129 134
130 cache.erase(old_path); 135 cache.erase(old_path);
131 file->Open(new_path, "r+b"); 136 file->Open(new_path, "r+b");
132 cache[new_path] = file; 137 cache.insert_or_assign(new_path, std::move(file));
133 } else { 138 } else {
134 UNREACHABLE(); 139 UNREACHABLE();
135 return nullptr; 140 return nullptr;
@@ -139,28 +144,33 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
139} 144}
140 145
141bool RealVfsFilesystem::DeleteFile(std::string_view path_) { 146bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
142 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 147 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
143 if (cache.find(path) != cache.end()) { 148 const auto cached_iter = cache.find(path);
144 if (!cache[path].expired()) 149
145 cache[path].lock()->Close(); 150 if (cached_iter != cache.cend()) {
151 if (!cached_iter->second.expired()) {
152 cached_iter->second.lock()->Close();
153 }
146 cache.erase(path); 154 cache.erase(path);
147 } 155 }
148 return FileUtil::Delete(path); 156
157 return FS::Delete(path);
149} 158}
150 159
151VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { 160VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
152 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 161 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
153 // Cannot use make_shared as RealVfsDirectory constructor is private 162 // Cannot use make_shared as RealVfsDirectory constructor is private
154 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 163 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
155} 164}
156 165
157VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { 166VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
158 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 167 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
159 const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash); 168 const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
160 if (!FileUtil::Exists(path)) { 169 if (!FS::Exists(path)) {
161 FileUtil::CreateFullPath(path_fwd); 170 FS::CreateFullPath(path_fwd);
162 if (!FileUtil::CreateDir(path)) 171 if (!FS::CreateDir(path)) {
163 return nullptr; 172 return nullptr;
173 }
164 } 174 }
165 // Cannot use make_shared as RealVfsDirectory constructor is private 175 // Cannot use make_shared as RealVfsDirectory constructor is private
166 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 176 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
@@ -168,67 +178,75 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms
168 178
169VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, 179VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
170 std::string_view new_path_) { 180 std::string_view new_path_) {
171 const auto old_path = 181 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
172 FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); 182 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
173 const auto new_path = 183 if (!FS::Exists(old_path) || FS::Exists(new_path) || !FS::IsDirectory(old_path)) {
174 FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault);
175 if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) ||
176 !FileUtil::IsDirectory(old_path))
177 return nullptr; 184 return nullptr;
178 FileUtil::CopyDir(old_path, new_path); 185 }
186 FS::CopyDir(old_path, new_path);
179 return OpenDirectory(new_path, Mode::ReadWrite); 187 return OpenDirectory(new_path, Mode::ReadWrite);
180} 188}
181 189
182VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, 190VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
183 std::string_view new_path_) { 191 std::string_view new_path_) {
184 const auto old_path = 192 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
185 FileUtil::SanitizePath(old_path_, FileUtil::DirectorySeparator::PlatformDefault); 193 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
186 const auto new_path = 194
187 FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); 195 if (!FS::Exists(old_path) || FS::Exists(new_path) || FS::IsDirectory(old_path) ||
188 if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || 196 !FS::Rename(old_path, new_path)) {
189 FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path))
190 return nullptr; 197 return nullptr;
198 }
191 199
192 for (auto& kv : cache) { 200 for (auto& kv : cache) {
193 // Path in cache starts with old_path 201 // If the path in the cache doesn't start with old_path, then bail on this file.
194 if (kv.first.rfind(old_path, 0) == 0) { 202 if (kv.first.rfind(old_path, 0) != 0) {
195 const auto file_old_path = 203 continue;
196 FileUtil::SanitizePath(kv.first, FileUtil::DirectorySeparator::PlatformDefault); 204 }
197 const auto file_new_path = 205
198 FileUtil::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()), 206 const auto file_old_path =
199 FileUtil::DirectorySeparator::PlatformDefault); 207 FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault);
200 auto cached = cache[file_old_path]; 208 auto file_new_path = FS::SanitizePath(new_path + DIR_SEP + kv.first.substr(old_path.size()),
201 if (!cached.expired()) { 209 FS::DirectorySeparator::PlatformDefault);
202 auto file = cached.lock(); 210 const auto& cached = cache[file_old_path];
203 file->Open(file_new_path, "r+b"); 211
204 cache.erase(file_old_path); 212 if (cached.expired()) {
205 cache[file_new_path] = file; 213 continue;
206 }
207 } 214 }
215
216 auto file = cached.lock();
217 file->Open(file_new_path, "r+b");
218 cache.erase(file_old_path);
219 cache.insert_or_assign(std::move(file_new_path), std::move(file));
208 } 220 }
209 221
210 return OpenDirectory(new_path, Mode::ReadWrite); 222 return OpenDirectory(new_path, Mode::ReadWrite);
211} 223}
212 224
213bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { 225bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
214 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 226 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
227
215 for (auto& kv : cache) { 228 for (auto& kv : cache) {
216 // Path in cache starts with old_path 229 // If the path in the cache doesn't start with path, then bail on this file.
217 if (kv.first.rfind(path, 0) == 0) { 230 if (kv.first.rfind(path, 0) != 0) {
218 if (!cache[kv.first].expired()) 231 continue;
219 cache[kv.first].lock()->Close(); 232 }
220 cache.erase(kv.first); 233
234 const auto& entry = cache[kv.first];
235 if (!entry.expired()) {
236 entry.lock()->Close();
221 } 237 }
238
239 cache.erase(kv.first);
222 } 240 }
223 return FileUtil::DeleteDirRecursively(path); 241
242 return FS::DeleteDirRecursively(path);
224} 243}
225 244
226RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOFile> backing_, 245RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_,
227 const std::string& path_, Mode perms_) 246 const std::string& path_, Mode perms_)
228 : base(base_), backing(std::move(backing_)), path(path_), 247 : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)),
229 parent_path(FileUtil::GetParentPath(path_)), 248 path_components(FS::SplitPathComponents(path_)),
230 path_components(FileUtil::SplitPathComponents(path_)), 249 parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
231 parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
232 perms(perms_) {} 250 perms(perms_) {}
233 251
234RealVfsFile::~RealVfsFile() = default; 252RealVfsFile::~RealVfsFile() = default;
@@ -258,14 +276,16 @@ bool RealVfsFile::IsReadable() const {
258} 276}
259 277
260std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 278std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
261 if (!backing->Seek(offset, SEEK_SET)) 279 if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
262 return 0; 280 return 0;
281 }
263 return backing->ReadBytes(data, length); 282 return backing->ReadBytes(data, length);
264} 283}
265 284
266std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 285std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
267 if (!backing->Seek(offset, SEEK_SET)) 286 if (!backing->Seek(static_cast<s64>(offset), SEEK_SET)) {
268 return 0; 287 return 0;
288 }
269 return backing->WriteBytes(data, length); 289 return backing->WriteBytes(data, length);
270} 290}
271 291
@@ -282,16 +302,18 @@ bool RealVfsFile::Close() {
282 302
283template <> 303template <>
284std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const { 304std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
285 if (perms == Mode::Append) 305 if (perms == Mode::Append) {
286 return {}; 306 return {};
307 }
287 308
288 std::vector<VirtualFile> out; 309 std::vector<VirtualFile> out;
289 FileUtil::ForeachDirectoryEntry( 310 FS::ForeachDirectoryEntry(
290 nullptr, path, 311 nullptr, path,
291 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { 312 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
292 const std::string full_path = directory + DIR_SEP + filename; 313 const std::string full_path = directory + DIR_SEP + filename;
293 if (!FileUtil::IsDirectory(full_path)) 314 if (!FS::IsDirectory(full_path)) {
294 out.emplace_back(base.OpenFile(full_path, perms)); 315 out.emplace_back(base.OpenFile(full_path, perms));
316 }
295 return true; 317 return true;
296 }); 318 });
297 319
@@ -300,16 +322,18 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
300 322
301template <> 323template <>
302std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const { 324std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
303 if (perms == Mode::Append) 325 if (perms == Mode::Append) {
304 return {}; 326 return {};
327 }
305 328
306 std::vector<VirtualDir> out; 329 std::vector<VirtualDir> out;
307 FileUtil::ForeachDirectoryEntry( 330 FS::ForeachDirectoryEntry(
308 nullptr, path, 331 nullptr, path,
309 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) { 332 [&out, this](u64* entries_out, const std::string& directory, const std::string& filename) {
310 const std::string full_path = directory + DIR_SEP + filename; 333 const std::string full_path = directory + DIR_SEP + filename;
311 if (FileUtil::IsDirectory(full_path)) 334 if (FS::IsDirectory(full_path)) {
312 out.emplace_back(base.OpenDirectory(full_path, perms)); 335 out.emplace_back(base.OpenDirectory(full_path, perms));
336 }
313 return true; 337 return true;
314 }); 338 });
315 339
@@ -317,29 +341,30 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
317} 341}
318 342
319RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) 343RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
320 : base(base_), path(FileUtil::RemoveTrailingSlash(path_)), 344 : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
321 parent_path(FileUtil::GetParentPath(path)), 345 path_components(FS::SplitPathComponents(path)),
322 path_components(FileUtil::SplitPathComponents(path)), 346 parent_components(FS::SliceVector(path_components, 0, path_components.size() - 1)),
323 parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
324 perms(perms_) { 347 perms(perms_) {
325 if (!FileUtil::Exists(path) && True(perms & Mode::WriteAppend)) { 348 if (!FS::Exists(path) && True(perms & Mode::WriteAppend)) {
326 FileUtil::CreateDir(path); 349 FS::CreateDir(path);
327 } 350 }
328} 351}
329 352
330RealVfsDirectory::~RealVfsDirectory() = default; 353RealVfsDirectory::~RealVfsDirectory() = default;
331 354
332std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { 355std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
333 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 356 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
334 if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path)) 357 if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
335 return nullptr; 358 return nullptr;
359 }
336 return base.OpenFile(full_path, perms); 360 return base.OpenFile(full_path, perms);
337} 361}
338 362
339std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { 363std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
340 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 364 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
341 if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path)) 365 if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
342 return nullptr; 366 return nullptr;
367 }
343 return base.OpenDirectory(full_path, perms); 368 return base.OpenDirectory(full_path, perms);
344} 369}
345 370
@@ -352,17 +377,17 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view
352} 377}
353 378
354std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) { 379std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
355 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 380 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
356 return base.CreateFile(full_path, perms); 381 return base.CreateFile(full_path, perms);
357} 382}
358 383
359std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { 384std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
360 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 385 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
361 return base.CreateDirectory(full_path, perms); 386 return base.CreateDirectory(full_path, perms);
362} 387}
363 388
364bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { 389bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
365 auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(name)); 390 const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(name));
366 return base.DeleteDirectory(full_path); 391 return base.DeleteDirectory(full_path);
367} 392}
368 393
@@ -387,8 +412,9 @@ std::string RealVfsDirectory::GetName() const {
387} 412}
388 413
389std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const { 414std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
390 if (path_components.size() <= 1) 415 if (path_components.size() <= 1) {
391 return nullptr; 416 return nullptr;
417 }
392 418
393 return base.OpenDirectory(parent_path, perms); 419 return base.OpenDirectory(parent_path, perms);
394} 420}
@@ -425,16 +451,17 @@ std::string RealVfsDirectory::GetFullPath() const {
425} 451}
426 452
427std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const { 453std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
428 if (perms == Mode::Append) 454 if (perms == Mode::Append) {
429 return {}; 455 return {};
456 }
430 457
431 std::map<std::string, VfsEntryType, std::less<>> out; 458 std::map<std::string, VfsEntryType, std::less<>> out;
432 FileUtil::ForeachDirectoryEntry( 459 FS::ForeachDirectoryEntry(
433 nullptr, path, 460 nullptr, path,
434 [&out](u64* entries_out, const std::string& directory, const std::string& filename) { 461 [&out](u64* entries_out, const std::string& directory, const std::string& filename) {
435 const std::string full_path = directory + DIR_SEP + filename; 462 const std::string full_path = directory + DIR_SEP + filename;
436 out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory 463 out.emplace(filename,
437 : VfsEntryType::File); 464 FS::IsDirectory(full_path) ? VfsEntryType::Directory : VfsEntryType::File);
438 return true; 465 return true;
439 }); 466 });
440 467
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index a0a857a31..0b537b22c 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -9,7 +9,7 @@
9#include "core/file_sys/mode.h" 9#include "core/file_sys/mode.h"
10#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
11 11
12namespace FileUtil { 12namespace Common::FS {
13class IOFile; 13class IOFile;
14} 14}
15 15
@@ -36,7 +36,7 @@ public:
36 bool DeleteDirectory(std::string_view path) override; 36 bool DeleteDirectory(std::string_view path) override;
37 37
38private: 38private:
39 boost::container::flat_map<std::string, std::weak_ptr<FileUtil::IOFile>> cache; 39 boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache;
40}; 40};
41 41
42// An implmentation of VfsFile that represents a file on the user's computer. 42// An implmentation of VfsFile that represents a file on the user's computer.
@@ -58,13 +58,13 @@ public:
58 bool Rename(std::string_view name) override; 58 bool Rename(std::string_view name) override;
59 59
60private: 60private:
61 RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing, 61 RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing,
62 const std::string& path, Mode perms = Mode::Read); 62 const std::string& path, Mode perms = Mode::Read);
63 63
64 bool Close(); 64 bool Close();
65 65
66 RealVfsFilesystem& base; 66 RealVfsFilesystem& base;
67 std::shared_ptr<FileUtil::IOFile> backing; 67 std::shared_ptr<Common::FS::IOFile> backing;
68 std::string path; 68 std::string path;
69 std::string parent_path; 69 std::string parent_path;
70 std::vector<std::string> path_components; 70 std::vector<std::string> path_components;
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index 81413c684..ccf5966d0 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -44,7 +44,7 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t
44} 44}
45 45
46NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { 46NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
47 std::string path = FileUtil::SanitizePath(file->GetFullPath()); 47 std::string path = Common::FS::SanitizePath(file->GetFullPath());
48 static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", 48 static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
49 std::regex_constants::ECMAScript | 49 std::regex_constants::ECMAScript |
50 std::regex_constants::icase); 50 std::regex_constants::icase);
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 63e4aeca0..eb54cb123 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -35,7 +35,7 @@ constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
35constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; 35constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
36 36
37static std::string GetImagePath(Common::UUID uuid) { 37static std::string GetImagePath(Common::UUID uuid) {
38 return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 38 return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
39 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 39 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
40} 40}
41 41
@@ -318,7 +318,7 @@ protected:
318 IPC::ResponseBuilder rb{ctx, 3}; 318 IPC::ResponseBuilder rb{ctx, 3};
319 rb.Push(RESULT_SUCCESS); 319 rb.Push(RESULT_SUCCESS);
320 320
321 const FileUtil::IOFile image(GetImagePath(user_id), "rb"); 321 const Common::FS::IOFile image(GetImagePath(user_id), "rb");
322 if (!image.IsOpen()) { 322 if (!image.IsOpen()) {
323 LOG_WARNING(Service_ACC, 323 LOG_WARNING(Service_ACC,
324 "Failed to load user provided image! Falling back to built-in backup..."); 324 "Failed to load user provided image! Falling back to built-in backup...");
@@ -340,7 +340,7 @@ protected:
340 IPC::ResponseBuilder rb{ctx, 3}; 340 IPC::ResponseBuilder rb{ctx, 3};
341 rb.Push(RESULT_SUCCESS); 341 rb.Push(RESULT_SUCCESS);
342 342
343 const FileUtil::IOFile image(GetImagePath(user_id), "rb"); 343 const Common::FS::IOFile image(GetImagePath(user_id), "rb");
344 344
345 if (!image.IsOpen()) { 345 if (!image.IsOpen()) {
346 LOG_WARNING(Service_ACC, 346 LOG_WARNING(Service_ACC,
@@ -405,7 +405,7 @@ protected:
405 ProfileData data; 405 ProfileData data;
406 std::memcpy(&data, user_data.data(), sizeof(ProfileData)); 406 std::memcpy(&data, user_data.data(), sizeof(ProfileData));
407 407
408 FileUtil::IOFile image(GetImagePath(user_id), "wb"); 408 Common::FS::IOFile image(GetImagePath(user_id), "wb");
409 409
410 if (!image.IsOpen() || !image.Resize(image_data.size()) || 410 if (!image.IsOpen() || !image.Resize(image_data.size()) ||
411 image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || 411 image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index a98d57b5c..9b829e957 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -13,6 +13,7 @@
13 13
14namespace Service::Account { 14namespace Service::Account {
15 15
16namespace FS = Common::FS;
16using Common::UUID; 17using Common::UUID;
17 18
18struct UserRaw { 19struct UserRaw {
@@ -318,9 +319,8 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
318} 319}
319 320
320void ProfileManager::ParseUserSaveFile() { 321void ProfileManager::ParseUserSaveFile() {
321 FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 322 const FS::IOFile save(
322 ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", 323 FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", "rb");
323 "rb");
324 324
325 if (!save.IsOpen()) { 325 if (!save.IsOpen()) {
326 LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " 326 LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
@@ -366,22 +366,22 @@ void ProfileManager::WriteUserSaveFile() {
366 }; 366 };
367 } 367 }
368 368
369 const auto raw_path = 369 const auto raw_path = FS::GetUserPath(FS::UserPath::NANDDir) + "/system/save/8000000000000010";
370 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"; 370 if (FS::Exists(raw_path) && !FS::IsDirectory(raw_path)) {
371 if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path)) 371 FS::Delete(raw_path);
372 FileUtil::Delete(raw_path); 372 }
373 373
374 const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 374 const auto path =
375 ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; 375 FS::GetUserPath(FS::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
376 376
377 if (!FileUtil::CreateFullPath(path)) { 377 if (!FS::CreateFullPath(path)) {
378 LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " 378 LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
379 "nand/system/save/8000000000000010/su/avators to mitigate this " 379 "nand/system/save/8000000000000010/su/avators to mitigate this "
380 "issue."); 380 "issue.");
381 return; 381 return;
382 } 382 }
383 383
384 FileUtil::IOFile save(path, "wb"); 384 FS::IOFile save(path, "wb");
385 385
386 if (!save.IsOpen()) { 386 if (!save.IsOpen()) {
387 LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " 387 LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 4157fbf39..efe595c4f 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -293,8 +293,8 @@ void WebBrowser::Finalize() {
293 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data))); 293 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data)));
294 broker.SignalStateChanged(); 294 broker.SignalStateChanged();
295 295
296 if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) { 296 if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
297 FileUtil::DeleteDirRecursively(temporary_dir); 297 Common::FS::DeleteDirRecursively(temporary_dir);
298 } 298 }
299} 299}
300 300
@@ -452,10 +452,10 @@ void WebBrowser::InitializeOffline() {
452 }; 452 };
453 453
454 temporary_dir = 454 temporary_dir =
455 FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" + 455 Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
456 WEB_SOURCE_NAMES[static_cast<u32>(source) - 1], 456 "web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
457 FileUtil::DirectorySeparator::PlatformDefault); 457 Common::FS::DirectorySeparator::PlatformDefault);
458 FileUtil::DeleteDirRecursively(temporary_dir); 458 Common::FS::DeleteDirRecursively(temporary_dir);
459 459
460 u64 title_id = 0; // 0 corresponds to current process 460 u64 title_id = 0; // 0 corresponds to current process
461 ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8); 461 ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
@@ -492,8 +492,8 @@ void WebBrowser::InitializeOffline() {
492 } 492 }
493 493
494 filename = 494 filename =
495 FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, 495 Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
496 FileUtil::DirectorySeparator::PlatformDefault); 496 Common::FS::DirectorySeparator::PlatformDefault);
497} 497}
498 498
499void WebBrowser::ExecuteShop() { 499void WebBrowser::ExecuteShop() {
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 51c2ba964..ca021a99f 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -89,12 +89,12 @@ constexpr u32 TIMEOUT_SECONDS = 30;
89 89
90std::string GetBINFilePath(u64 title_id) { 90std::string GetBINFilePath(u64 title_id) {
91 return fmt::format("{}bcat/{:016X}/launchparam.bin", 91 return fmt::format("{}bcat/{:016X}/launchparam.bin",
92 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); 92 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
93} 93}
94 94
95std::string GetZIPFilePath(u64 title_id) { 95std::string GetZIPFilePath(u64 title_id) {
96 return fmt::format("{}bcat/{:016X}/data.zip", 96 return fmt::format("{}bcat/{:016X}/data.zip",
97 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); 97 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), title_id);
98} 98}
99 99
100// If the error is something the user should know about (build ID mismatch, bad client version), 100// If the error is something the user should know about (build ID mismatch, bad client version),
@@ -205,8 +205,8 @@ private:
205 {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)}, 205 {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
206 }; 206 };
207 207
208 if (FileUtil::Exists(path)) { 208 if (Common::FS::Exists(path)) {
209 FileUtil::IOFile file{path, "rb"}; 209 Common::FS::IOFile file{path, "rb"};
210 if (file.IsOpen()) { 210 if (file.IsOpen()) {
211 std::vector<u8> bytes(file.GetSize()); 211 std::vector<u8> bytes(file.GetSize());
212 file.ReadBytes(bytes.data(), bytes.size()); 212 file.ReadBytes(bytes.data(), bytes.size());
@@ -236,8 +236,8 @@ private:
236 return DownloadResult::InvalidContentType; 236 return DownloadResult::InvalidContentType;
237 } 237 }
238 238
239 FileUtil::CreateFullPath(path); 239 Common::FS::CreateFullPath(path);
240 FileUtil::IOFile file{path, "wb"}; 240 Common::FS::IOFile file{path, "wb"};
241 if (!file.IsOpen()) 241 if (!file.IsOpen())
242 return DownloadResult::GeneralFSError; 242 return DownloadResult::GeneralFSError;
243 if (!file.Resize(response->body.size())) 243 if (!file.Resize(response->body.size()))
@@ -290,7 +290,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
290 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 290 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
291 291
292 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 292 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
293 FileUtil::Delete(zip_path); 293 Common::FS::Delete(zip_path);
294 } 294 }
295 295
296 HandleDownloadDisplayResult(applet_manager, res); 296 HandleDownloadDisplayResult(applet_manager, res);
@@ -300,7 +300,7 @@ void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGe
300 300
301 progress.StartProcessingDataList(); 301 progress.StartProcessingDataList();
302 302
303 FileUtil::IOFile zip{zip_path, "rb"}; 303 Common::FS::IOFile zip{zip_path, "rb"};
304 const auto size = zip.GetSize(); 304 const auto size = zip.GetSize();
305 std::vector<u8> bytes(size); 305 std::vector<u8> bytes(size);
306 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { 306 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
@@ -420,7 +420,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
420 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 420 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
421 421
422 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { 422 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
423 FileUtil::Delete(path); 423 Common::FS::Delete(path);
424 } 424 }
425 425
426 HandleDownloadDisplayResult(applet_manager, res); 426 HandleDownloadDisplayResult(applet_manager, res);
@@ -428,7 +428,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
428 } 428 }
429 } 429 }
430 430
431 FileUtil::IOFile bin{path, "rb"}; 431 Common::FS::IOFile bin{path, "rb"};
432 const auto size = bin.GetSize(); 432 const auto size = bin.GetSize();
433 std::vector<u8> bytes(size); 433 std::vector<u8> bytes(size);
434 if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { 434 if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 4490f8e4c..2cee1193c 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -36,7 +36,7 @@ constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
36 36
37static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, 37static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
38 std::string_view dir_name_) { 38 std::string_view dir_name_) {
39 std::string dir_name(FileUtil::SanitizePath(dir_name_)); 39 std::string dir_name(Common::FS::SanitizePath(dir_name_));
40 if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\") 40 if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\")
41 return base; 41 return base;
42 42
@@ -53,13 +53,13 @@ std::string VfsDirectoryServiceWrapper::GetName() const {
53} 53}
54 54
55ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const { 55ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size) const {
56 std::string path(FileUtil::SanitizePath(path_)); 56 std::string path(Common::FS::SanitizePath(path_));
57 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 57 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
58 // dir can be nullptr if path contains subdirectories, create those prior to creating the file. 58 // dir can be nullptr if path contains subdirectories, create those prior to creating the file.
59 if (dir == nullptr) { 59 if (dir == nullptr) {
60 dir = backing->CreateSubdirectory(FileUtil::GetParentPath(path)); 60 dir = backing->CreateSubdirectory(Common::FS::GetParentPath(path));
61 } 61 }
62 auto file = dir->CreateFile(FileUtil::GetFilename(path)); 62 auto file = dir->CreateFile(Common::FS::GetFilename(path));
63 if (file == nullptr) { 63 if (file == nullptr) {
64 // TODO(DarkLordZach): Find a better error code for this 64 // TODO(DarkLordZach): Find a better error code for this
65 return RESULT_UNKNOWN; 65 return RESULT_UNKNOWN;
@@ -72,17 +72,17 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
72} 72}
73 73
74ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { 74ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
75 std::string path(FileUtil::SanitizePath(path_)); 75 std::string path(Common::FS::SanitizePath(path_));
76 if (path.empty()) { 76 if (path.empty()) {
77 // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but... 77 // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
78 return RESULT_SUCCESS; 78 return RESULT_SUCCESS;
79 } 79 }
80 80
81 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 81 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
82 if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) { 82 if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
83 return FileSys::ERROR_PATH_NOT_FOUND; 83 return FileSys::ERROR_PATH_NOT_FOUND;
84 } 84 }
85 if (!dir->DeleteFile(FileUtil::GetFilename(path))) { 85 if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
86 // TODO(DarkLordZach): Find a better error code for this 86 // TODO(DarkLordZach): Find a better error code for this
87 return RESULT_UNKNOWN; 87 return RESULT_UNKNOWN;
88 } 88 }
@@ -91,11 +91,11 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
91} 91}
92 92
93ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { 93ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
94 std::string path(FileUtil::SanitizePath(path_)); 94 std::string path(Common::FS::SanitizePath(path_));
95 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 95 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
96 if (dir == nullptr && FileUtil::GetFilename(FileUtil::GetParentPath(path)).empty()) 96 if (dir == nullptr && Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty())
97 dir = backing; 97 dir = backing;
98 auto new_dir = dir->CreateSubdirectory(FileUtil::GetFilename(path)); 98 auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path));
99 if (new_dir == nullptr) { 99 if (new_dir == nullptr) {
100 // TODO(DarkLordZach): Find a better error code for this 100 // TODO(DarkLordZach): Find a better error code for this
101 return RESULT_UNKNOWN; 101 return RESULT_UNKNOWN;
@@ -104,9 +104,9 @@ ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_)
104} 104}
105 105
106ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const { 106ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const {
107 std::string path(FileUtil::SanitizePath(path_)); 107 std::string path(Common::FS::SanitizePath(path_));
108 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 108 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
109 if (!dir->DeleteSubdirectory(FileUtil::GetFilename(path))) { 109 if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) {
110 // TODO(DarkLordZach): Find a better error code for this 110 // TODO(DarkLordZach): Find a better error code for this
111 return RESULT_UNKNOWN; 111 return RESULT_UNKNOWN;
112 } 112 }
@@ -114,9 +114,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_)
114} 114}
115 115
116ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const { 116ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const {
117 std::string path(FileUtil::SanitizePath(path_)); 117 std::string path(Common::FS::SanitizePath(path_));
118 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 118 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
119 if (!dir->DeleteSubdirectoryRecursive(FileUtil::GetFilename(path))) { 119 if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) {
120 // TODO(DarkLordZach): Find a better error code for this 120 // TODO(DarkLordZach): Find a better error code for this
121 return RESULT_UNKNOWN; 121 return RESULT_UNKNOWN;
122 } 122 }
@@ -124,10 +124,10 @@ ResultCode VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::str
124} 124}
125 125
126ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const { 126ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const {
127 const std::string sanitized_path(FileUtil::SanitizePath(path)); 127 const std::string sanitized_path(Common::FS::SanitizePath(path));
128 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(sanitized_path)); 128 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path));
129 129
130 if (!dir->CleanSubdirectoryRecursive(FileUtil::GetFilename(sanitized_path))) { 130 if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) {
131 // TODO(DarkLordZach): Find a better error code for this 131 // TODO(DarkLordZach): Find a better error code for this
132 return RESULT_UNKNOWN; 132 return RESULT_UNKNOWN;
133 } 133 }
@@ -137,14 +137,14 @@ ResultCode VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::stri
137 137
138ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, 138ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
139 const std::string& dest_path_) const { 139 const std::string& dest_path_) const {
140 std::string src_path(FileUtil::SanitizePath(src_path_)); 140 std::string src_path(Common::FS::SanitizePath(src_path_));
141 std::string dest_path(FileUtil::SanitizePath(dest_path_)); 141 std::string dest_path(Common::FS::SanitizePath(dest_path_));
142 auto src = backing->GetFileRelative(src_path); 142 auto src = backing->GetFileRelative(src_path);
143 if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { 143 if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
144 // Use more-optimized vfs implementation rename. 144 // Use more-optimized vfs implementation rename.
145 if (src == nullptr) 145 if (src == nullptr)
146 return FileSys::ERROR_PATH_NOT_FOUND; 146 return FileSys::ERROR_PATH_NOT_FOUND;
147 if (!src->Rename(FileUtil::GetFilename(dest_path))) { 147 if (!src->Rename(Common::FS::GetFilename(dest_path))) {
148 // TODO(DarkLordZach): Find a better error code for this 148 // TODO(DarkLordZach): Find a better error code for this
149 return RESULT_UNKNOWN; 149 return RESULT_UNKNOWN;
150 } 150 }
@@ -162,7 +162,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
162 ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(), 162 ASSERT_MSG(dest->WriteBytes(src->ReadAllBytes()) == src->GetSize(),
163 "Could not write all of the bytes but everything else has succeded."); 163 "Could not write all of the bytes but everything else has succeded.");
164 164
165 if (!src->GetContainingDirectory()->DeleteFile(FileUtil::GetFilename(src_path))) { 165 if (!src->GetContainingDirectory()->DeleteFile(Common::FS::GetFilename(src_path))) {
166 // TODO(DarkLordZach): Find a better error code for this 166 // TODO(DarkLordZach): Find a better error code for this
167 return RESULT_UNKNOWN; 167 return RESULT_UNKNOWN;
168 } 168 }
@@ -172,14 +172,14 @@ ResultCode VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_,
172 172
173ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, 173ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
174 const std::string& dest_path_) const { 174 const std::string& dest_path_) const {
175 std::string src_path(FileUtil::SanitizePath(src_path_)); 175 std::string src_path(Common::FS::SanitizePath(src_path_));
176 std::string dest_path(FileUtil::SanitizePath(dest_path_)); 176 std::string dest_path(Common::FS::SanitizePath(dest_path_));
177 auto src = GetDirectoryRelativeWrapped(backing, src_path); 177 auto src = GetDirectoryRelativeWrapped(backing, src_path);
178 if (FileUtil::GetParentPath(src_path) == FileUtil::GetParentPath(dest_path)) { 178 if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
179 // Use more-optimized vfs implementation rename. 179 // Use more-optimized vfs implementation rename.
180 if (src == nullptr) 180 if (src == nullptr)
181 return FileSys::ERROR_PATH_NOT_FOUND; 181 return FileSys::ERROR_PATH_NOT_FOUND;
182 if (!src->Rename(FileUtil::GetFilename(dest_path))) { 182 if (!src->Rename(Common::FS::GetFilename(dest_path))) {
183 // TODO(DarkLordZach): Find a better error code for this 183 // TODO(DarkLordZach): Find a better error code for this
184 return RESULT_UNKNOWN; 184 return RESULT_UNKNOWN;
185 } 185 }
@@ -198,7 +198,7 @@ ResultCode VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_pa
198 198
199ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_, 199ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_,
200 FileSys::Mode mode) const { 200 FileSys::Mode mode) const {
201 const std::string path(FileUtil::SanitizePath(path_)); 201 const std::string path(Common::FS::SanitizePath(path_));
202 std::string_view npath = path; 202 std::string_view npath = path;
203 while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) { 203 while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) {
204 npath.remove_prefix(1); 204 npath.remove_prefix(1);
@@ -218,7 +218,7 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::
218} 218}
219 219
220ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) { 220ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) {
221 std::string path(FileUtil::SanitizePath(path_)); 221 std::string path(Common::FS::SanitizePath(path_));
222 auto dir = GetDirectoryRelativeWrapped(backing, path); 222 auto dir = GetDirectoryRelativeWrapped(backing, path);
223 if (dir == nullptr) { 223 if (dir == nullptr) {
224 // TODO(DarkLordZach): Find a better error code for this 224 // TODO(DarkLordZach): Find a better error code for this
@@ -229,11 +229,11 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s
229 229
230ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( 230ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
231 const std::string& path_) const { 231 const std::string& path_) const {
232 std::string path(FileUtil::SanitizePath(path_)); 232 std::string path(Common::FS::SanitizePath(path_));
233 auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); 233 auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
234 if (dir == nullptr) 234 if (dir == nullptr)
235 return FileSys::ERROR_PATH_NOT_FOUND; 235 return FileSys::ERROR_PATH_NOT_FOUND;
236 auto filename = FileUtil::GetFilename(path); 236 auto filename = Common::FS::GetFilename(path);
237 // TODO(Subv): Some games use the '/' path, find out what this means. 237 // TODO(Subv): Some games use the '/' path, find out what this means.
238 if (filename.empty()) 238 if (filename.empty())
239 return MakeResult(FileSys::EntryType::Directory); 239 return MakeResult(FileSys::EntryType::Directory);
@@ -695,13 +695,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
695 sdmc_factory = nullptr; 695 sdmc_factory = nullptr;
696 } 696 }
697 697
698 auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 698 auto nand_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir),
699 FileSys::Mode::ReadWrite); 699 FileSys::Mode::ReadWrite);
700 auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 700 auto sd_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir),
701 FileSys::Mode::ReadWrite); 701 FileSys::Mode::ReadWrite);
702 auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), 702 auto load_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir),
703 FileSys::Mode::ReadWrite); 703 FileSys::Mode::ReadWrite);
704 auto dump_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), 704 auto dump_directory = vfs.OpenDirectory(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir),
705 FileSys::Mode::ReadWrite); 705 FileSys::Mode::ReadWrite);
706 706
707 if (bis_factory == nullptr) { 707 if (bis_factory == nullptr) {
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 7c48e55e1..9bc3a8840 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -67,7 +67,7 @@ FileType GuessFromFilename(const std::string& name) {
67 return FileType::NCA; 67 return FileType::NCA;
68 68
69 const std::string extension = 69 const std::string extension =
70 Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name))); 70 Common::ToLower(std::string(Common::FS::GetExtensionFromFilename(name)));
71 71
72 if (extension == "elf") 72 if (extension == "elf")
73 return FileType::ELF; 73 return FileType::ELF;
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index b899ac884..b93396a80 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -38,11 +38,11 @@ PerfStats::~PerfStats() {
38 std::ostringstream stream; 38 std::ostringstream stream;
39 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, 39 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
40 std::ostream_iterator<double>(stream, "\n")); 40 std::ostream_iterator<double>(stream, "\n"));
41 const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); 41 const std::string& path = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
42 // %F Date format expanded is "%Y-%m-%d" 42 // %F Date format expanded is "%Y-%m-%d"
43 const std::string filename = 43 const std::string filename =
44 fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); 44 fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
45 FileUtil::IOFile file(filename, "w"); 45 Common::FS::IOFile file(filename, "w");
46 file.WriteString(stream.str()); 46 file.WriteString(stream.str());
47} 47}
48 48
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 76cfa5a17..0becdf642 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -28,8 +28,9 @@
28namespace { 28namespace {
29 29
30std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { 30std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
31 return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir), 31 return fmt::format("{}{}/{:016X}_{}.json",
32 type, title_id, timestamp); 32 Common::FS::GetUserPath(Common::FS::UserPath::LogDir), type, title_id,
33 timestamp);
33} 34}
34 35
35std::string GetTimestamp() { 36std::string GetTimestamp() {
@@ -40,13 +41,13 @@ std::string GetTimestamp() {
40using namespace nlohmann; 41using namespace nlohmann;
41 42
42void SaveToFile(json json, const std::string& filename) { 43void SaveToFile(json json, const std::string& filename) {
43 if (!FileUtil::CreateFullPath(filename)) { 44 if (!Common::FS::CreateFullPath(filename)) {
44 LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename); 45 LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
45 return; 46 return;
46 } 47 }
47 48
48 std::ofstream file( 49 std::ofstream file(
49 FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault)); 50 Common::FS::SanitizePath(filename, Common::FS::DirectorySeparator::PlatformDefault));
50 file << std::setw(4) << json << std::endl; 51 file << std::setw(4) << json << std::endl;
51} 52}
52 53
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 416b2d866..d328fb8b7 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -121,8 +121,8 @@ void LogSettings() {
121 log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); 121 log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
122 log_setting("Audio_OutputDevice", values.audio_device_id); 122 log_setting("Audio_OutputDevice", values.audio_device_id);
123 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd); 123 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
124 log_setting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)); 124 log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
125 log_setting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)); 125 log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
126 log_setting("Debugging_UseGdbstub", values.use_gdbstub); 126 log_setting("Debugging_UseGdbstub", values.use_gdbstub);
127 log_setting("Debugging_GdbstubPort", values.gdbstub_port); 127 log_setting("Debugging_GdbstubPort", values.gdbstub_port);
128 log_setting("Debugging_ProgramArgs", values.program_args); 128 log_setting("Debugging_ProgramArgs", values.program_args);
diff --git a/src/core/settings.h b/src/core/settings.h
index bb145f193..3681b5e9d 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -359,7 +359,8 @@ enum class GPUAccuracy : u32 {
359 359
360enum class CPUAccuracy { 360enum class CPUAccuracy {
361 Accurate = 0, 361 Accurate = 0,
362 DebugMode = 1, 362 Unsafe = 1,
363 DebugMode = 2,
363}; 364};
364 365
365extern bool configuring_global; 366extern bool configuring_global;
@@ -419,6 +420,9 @@ struct Values {
419 bool cpuopt_misc_ir; 420 bool cpuopt_misc_ir;
420 bool cpuopt_reduce_misalign_checks; 421 bool cpuopt_reduce_misalign_checks;
421 422
423 bool cpuopt_unsafe_unfuse_fma;
424 bool cpuopt_unsafe_reduce_fp_error;
425
422 // Renderer 426 // Renderer
423 Setting<RendererBackend> renderer_backend; 427 Setting<RendererBackend> renderer_backend;
424 bool renderer_debug; 428 bool renderer_debug;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 5a30c75da..da09c0dbc 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -25,6 +25,8 @@
25 25
26namespace Core { 26namespace Core {
27 27
28namespace Telemetry = Common::Telemetry;
29
28static u64 GenerateTelemetryId() { 30static u64 GenerateTelemetryId() {
29 u64 telemetry_id{}; 31 u64 telemetry_id{};
30 32
@@ -70,12 +72,12 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
70 72
71u64 GetTelemetryId() { 73u64 GetTelemetryId() {
72 u64 telemetry_id{}; 74 u64 telemetry_id{};
73 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 75 const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
74 "telemetry_id"}; 76 "telemetry_id"};
75 77
76 bool generate_new_id = !FileUtil::Exists(filename); 78 bool generate_new_id = !Common::FS::Exists(filename);
77 if (!generate_new_id) { 79 if (!generate_new_id) {
78 FileUtil::IOFile file(filename, "rb"); 80 Common::FS::IOFile file(filename, "rb");
79 if (!file.IsOpen()) { 81 if (!file.IsOpen()) {
80 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 82 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
81 return {}; 83 return {};
@@ -88,7 +90,7 @@ u64 GetTelemetryId() {
88 } 90 }
89 91
90 if (generate_new_id) { 92 if (generate_new_id) {
91 FileUtil::IOFile file(filename, "wb"); 93 Common::FS::IOFile file(filename, "wb");
92 if (!file.IsOpen()) { 94 if (!file.IsOpen()) {
93 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 95 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
94 return {}; 96 return {};
@@ -102,10 +104,10 @@ u64 GetTelemetryId() {
102 104
103u64 RegenerateTelemetryId() { 105u64 RegenerateTelemetryId() {
104 const u64 new_telemetry_id{GenerateTelemetryId()}; 106 const u64 new_telemetry_id{GenerateTelemetryId()};
105 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 107 const std::string filename{Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir) +
106 "telemetry_id"}; 108 "telemetry_id"};
107 109
108 FileUtil::IOFile file(filename, "wb"); 110 Common::FS::IOFile file(filename, "wb");
109 if (!file.IsOpen()) { 111 if (!file.IsOpen()) {
110 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 112 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
111 return {}; 113 return {};
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 17ac22377..66789d4bd 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -52,7 +52,7 @@ public:
52 * @param value Value for the field to add. 52 * @param value Value for the field to add.
53 */ 53 */
54 template <typename T> 54 template <typename T>
55 void AddField(Telemetry::FieldType type, const char* name, T value) { 55 void AddField(Common::Telemetry::FieldType type, const char* name, T value) {
56 field_collection.AddField(type, name, std::move(value)); 56 field_collection.AddField(type, name, std::move(value));
57 } 57 }
58 58
@@ -63,7 +63,8 @@ public:
63 bool SubmitTestcase(); 63 bool SubmitTestcase();
64 64
65private: 65private:
66 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session 66 /// Tracks all added fields for the session
67 Common::Telemetry::FieldCollection field_collection;
67}; 68};
68 69
69/** 70/**
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index 8b2a6a42c..06cc12d5a 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -5,15 +5,10 @@
5#pragma once 5#pragma once
6 6
7#include <algorithm> 7#include <algorithm>
8#include <array>
9#include <memory>
10#include <queue> 8#include <queue>
11 9
12#include "common/assert.h"
13#include "common/common_types.h" 10#include "common/common_types.h"
14#include "core/core.h" 11#include "core/core.h"
15#include "core/memory.h"
16#include "core/settings.h"
17#include "video_core/gpu.h" 12#include "video_core/gpu.h"
18#include "video_core/memory_manager.h" 13#include "video_core/memory_manager.h"
19#include "video_core/rasterizer_interface.h" 14#include "video_core/rasterizer_interface.h"
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
index aa5256419..bd01fd1f2 100644
--- a/src/video_core/macro/macro_interpreter.cpp
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -34,7 +34,6 @@ void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 metho
34 this->parameters = std::make_unique<u32[]>(num_parameters); 34 this->parameters = std::make_unique<u32[]>(num_parameters);
35 } 35 }
36 std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32)); 36 std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32));
37 this->num_parameters = num_parameters;
38 37
39 // Execute the code until we hit an exit condition. 38 // Execute the code until we hit an exit condition.
40 bool keep_executing = true; 39 bool keep_executing = true;
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp
index ec5421afa..3d2588dd2 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp
@@ -4,16 +4,17 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6 6
7#include <glad/glad.h>
8
7#include "video_core/renderer_opengl/gl_buffer_cache.h" 9#include "video_core/renderer_opengl/gl_buffer_cache.h"
8#include "video_core/renderer_opengl/gl_fence_manager.h" 10#include "video_core/renderer_opengl/gl_fence_manager.h"
9 11
10namespace OpenGL { 12namespace OpenGL {
11 13
12GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) 14GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {}
13 : VideoCommon::FenceBase(payload, is_stubbed), sync_object{} {}
14 15
15GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed) 16GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed)
16 : VideoCommon::FenceBase(address, payload, is_stubbed), sync_object{} {} 17 : FenceBase(address, payload, is_stubbed) {}
17 18
18GLInnerFence::~GLInnerFence() = default; 19GLInnerFence::~GLInnerFence() = default;
19 20
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
index c917b3343..1686cf5c8 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.h
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -5,7 +5,6 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <glad/glad.h>
9 8
10#include "common/common_types.h" 9#include "common/common_types.h"
11#include "video_core/fence_manager.h" 10#include "video_core/fence_manager.h"
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index cb284db77..4af5824cd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -177,15 +177,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWind
177 } 177 }
178 178
179 if (device.UseAsynchronousShaders()) { 179 if (device.UseAsynchronousShaders()) {
180 // Max worker threads we should allow 180 async_shaders.AllocateWorkers();
181 constexpr u32 MAX_THREADS = 4;
182 // Deduce how many threads we can use
183 const u32 threads_used = std::thread::hardware_concurrency() / 4;
184 // Always allow at least 1 thread regardless of our settings
185 const auto max_worker_count = std::max(1U, threads_used);
186 // Don't use more than MAX_THREADS
187 const auto worker_count = std::min(max_worker_count, MAX_THREADS);
188 async_shaders.AllocateWorkers(worker_count);
189 } 181 }
190} 182}
191 183
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 2dcc2b0eb..40c0877c1 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -73,7 +73,7 @@ ShaderDiskCacheEntry::ShaderDiskCacheEntry() = default;
73 73
74ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default; 74ShaderDiskCacheEntry::~ShaderDiskCacheEntry() = default;
75 75
76bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) { 76bool ShaderDiskCacheEntry::Load(Common::FS::IOFile& file) {
77 if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) { 77 if (file.ReadBytes(&type, sizeof(u32)) != sizeof(u32)) {
78 return false; 78 return false;
79 } 79 }
@@ -144,7 +144,7 @@ bool ShaderDiskCacheEntry::Load(FileUtil::IOFile& file) {
144 return true; 144 return true;
145} 145}
146 146
147bool ShaderDiskCacheEntry::Save(FileUtil::IOFile& file) const { 147bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
148 if (file.WriteObject(static_cast<u32>(type)) != 1 || 148 if (file.WriteObject(static_cast<u32>(type)) != 1 ||
149 file.WriteObject(static_cast<u32>(code.size())) != 1 || 149 file.WriteObject(static_cast<u32>(code.size())) != 1 ||
150 file.WriteObject(static_cast<u32>(code_b.size())) != 1) { 150 file.WriteObject(static_cast<u32>(code_b.size())) != 1) {
@@ -214,20 +214,20 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
214 // Skip games without title id 214 // Skip games without title id
215 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; 215 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
216 if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) { 216 if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
217 return {}; 217 return std::nullopt;
218 } 218 }
219 219
220 FileUtil::IOFile file(GetTransferablePath(), "rb"); 220 Common::FS::IOFile file(GetTransferablePath(), "rb");
221 if (!file.IsOpen()) { 221 if (!file.IsOpen()) {
222 LOG_INFO(Render_OpenGL, "No transferable shader cache found"); 222 LOG_INFO(Render_OpenGL, "No transferable shader cache found");
223 is_usable = true; 223 is_usable = true;
224 return {}; 224 return std::nullopt;
225 } 225 }
226 226
227 u32 version{}; 227 u32 version{};
228 if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) { 228 if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) {
229 LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it"); 229 LOG_ERROR(Render_OpenGL, "Failed to get transferable cache version, skipping it");
230 return {}; 230 return std::nullopt;
231 } 231 }
232 232
233 if (version < NativeVersion) { 233 if (version < NativeVersion) {
@@ -235,12 +235,12 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
235 file.Close(); 235 file.Close();
236 InvalidateTransferable(); 236 InvalidateTransferable();
237 is_usable = true; 237 is_usable = true;
238 return {}; 238 return std::nullopt;
239 } 239 }
240 if (version > NativeVersion) { 240 if (version > NativeVersion) {
241 LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version " 241 LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version "
242 "of the emulator, skipping"); 242 "of the emulator, skipping");
243 return {}; 243 return std::nullopt;
244 } 244 }
245 245
246 // Version is valid, load the shaders 246 // Version is valid, load the shaders
@@ -249,7 +249,7 @@ std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTran
249 ShaderDiskCacheEntry& entry = entries.emplace_back(); 249 ShaderDiskCacheEntry& entry = entries.emplace_back();
250 if (!entry.Load(file)) { 250 if (!entry.Load(file)) {
251 LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping"); 251 LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry, skipping");
252 return {}; 252 return std::nullopt;
253 } 253 }
254 } 254 }
255 255
@@ -262,7 +262,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
262 return {}; 262 return {};
263 } 263 }
264 264
265 FileUtil::IOFile file(GetPrecompiledPath(), "rb"); 265 Common::FS::IOFile file(GetPrecompiledPath(), "rb");
266 if (!file.IsOpen()) { 266 if (!file.IsOpen()) {
267 LOG_INFO(Render_OpenGL, "No precompiled shader cache found"); 267 LOG_INFO(Render_OpenGL, "No precompiled shader cache found");
268 return {}; 268 return {};
@@ -279,7 +279,7 @@ std::vector<ShaderDiskCachePrecompiled> ShaderDiskCacheOpenGL::LoadPrecompiled()
279} 279}
280 280
281std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile( 281std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::LoadPrecompiledFile(
282 FileUtil::IOFile& file) { 282 Common::FS::IOFile& file) {
283 // Read compressed file from disk and decompress to virtual precompiled cache file 283 // Read compressed file from disk and decompress to virtual precompiled cache file
284 std::vector<u8> compressed(file.GetSize()); 284 std::vector<u8> compressed(file.GetSize());
285 file.ReadBytes(compressed.data(), compressed.size()); 285 file.ReadBytes(compressed.data(), compressed.size());
@@ -290,12 +290,12 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
290 ShaderCacheVersionHash file_hash{}; 290 ShaderCacheVersionHash file_hash{};
291 if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) { 291 if (!LoadArrayFromPrecompiled(file_hash.data(), file_hash.size())) {
292 precompiled_cache_virtual_file_offset = 0; 292 precompiled_cache_virtual_file_offset = 0;
293 return {}; 293 return std::nullopt;
294 } 294 }
295 if (GetShaderCacheVersionHash() != file_hash) { 295 if (GetShaderCacheVersionHash() != file_hash) {
296 LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator"); 296 LOG_INFO(Render_OpenGL, "Precompiled cache is from another version of the emulator");
297 precompiled_cache_virtual_file_offset = 0; 297 precompiled_cache_virtual_file_offset = 0;
298 return {}; 298 return std::nullopt;
299 } 299 }
300 300
301 std::vector<ShaderDiskCachePrecompiled> entries; 301 std::vector<ShaderDiskCachePrecompiled> entries;
@@ -305,19 +305,20 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
305 if (!LoadObjectFromPrecompiled(entry.unique_identifier) || 305 if (!LoadObjectFromPrecompiled(entry.unique_identifier) ||
306 !LoadObjectFromPrecompiled(entry.binary_format) || 306 !LoadObjectFromPrecompiled(entry.binary_format) ||
307 !LoadObjectFromPrecompiled(binary_size)) { 307 !LoadObjectFromPrecompiled(binary_size)) {
308 return {}; 308 return std::nullopt;
309 } 309 }
310 310
311 entry.binary.resize(binary_size); 311 entry.binary.resize(binary_size);
312 if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) { 312 if (!LoadArrayFromPrecompiled(entry.binary.data(), entry.binary.size())) {
313 return {}; 313 return std::nullopt;
314 } 314 }
315 } 315 }
316 return entries; 316
317 return std::move(entries);
317} 318}
318 319
319void ShaderDiskCacheOpenGL::InvalidateTransferable() { 320void ShaderDiskCacheOpenGL::InvalidateTransferable() {
320 if (!FileUtil::Delete(GetTransferablePath())) { 321 if (!Common::FS::Delete(GetTransferablePath())) {
321 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}", 322 LOG_ERROR(Render_OpenGL, "Failed to invalidate transferable file={}",
322 GetTransferablePath()); 323 GetTransferablePath());
323 } 324 }
@@ -328,7 +329,7 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
328 // Clear virtaul precompiled cache file 329 // Clear virtaul precompiled cache file
329 precompiled_cache_virtual_file.Resize(0); 330 precompiled_cache_virtual_file.Resize(0);
330 331
331 if (!FileUtil::Delete(GetPrecompiledPath())) { 332 if (!Common::FS::Delete(GetPrecompiledPath())) {
332 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath()); 333 LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
333 } 334 }
334} 335}
@@ -344,7 +345,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) {
344 return; 345 return;
345 } 346 }
346 347
347 FileUtil::IOFile file = AppendTransferableFile(); 348 Common::FS::IOFile file = AppendTransferableFile();
348 if (!file.IsOpen()) { 349 if (!file.IsOpen()) {
349 return; 350 return;
350 } 351 }
@@ -386,15 +387,15 @@ void ShaderDiskCacheOpenGL::SavePrecompiled(u64 unique_identifier, GLuint progra
386 } 387 }
387} 388}
388 389
389FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { 390Common::FS::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
390 if (!EnsureDirectories()) { 391 if (!EnsureDirectories()) {
391 return {}; 392 return {};
392 } 393 }
393 394
394 const auto transferable_path{GetTransferablePath()}; 395 const auto transferable_path{GetTransferablePath()};
395 const bool existed = FileUtil::Exists(transferable_path); 396 const bool existed = Common::FS::Exists(transferable_path);
396 397
397 FileUtil::IOFile file(transferable_path, "ab"); 398 Common::FS::IOFile file(transferable_path, "ab");
398 if (!file.IsOpen()) { 399 if (!file.IsOpen()) {
399 LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path); 400 LOG_ERROR(Render_OpenGL, "Failed to open transferable cache in path={}", transferable_path);
400 return {}; 401 return {};
@@ -426,7 +427,7 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
426 Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size()); 427 Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
427 428
428 const auto precompiled_path{GetPrecompiledPath()}; 429 const auto precompiled_path{GetPrecompiledPath()};
429 FileUtil::IOFile file(precompiled_path, "wb"); 430 Common::FS::IOFile file(precompiled_path, "wb");
430 431
431 if (!file.IsOpen()) { 432 if (!file.IsOpen()) {
432 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path); 433 LOG_ERROR(Render_OpenGL, "Failed to open precompiled cache in path={}", precompiled_path);
@@ -440,24 +441,24 @@ void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
440 441
441bool ShaderDiskCacheOpenGL::EnsureDirectories() const { 442bool ShaderDiskCacheOpenGL::EnsureDirectories() const {
442 const auto CreateDir = [](const std::string& dir) { 443 const auto CreateDir = [](const std::string& dir) {
443 if (!FileUtil::CreateDir(dir)) { 444 if (!Common::FS::CreateDir(dir)) {
444 LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir); 445 LOG_ERROR(Render_OpenGL, "Failed to create directory={}", dir);
445 return false; 446 return false;
446 } 447 }
447 return true; 448 return true;
448 }; 449 };
449 450
450 return CreateDir(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)) && 451 return CreateDir(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)) &&
451 CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) && 452 CreateDir(GetBaseDir()) && CreateDir(GetTransferableDir()) &&
452 CreateDir(GetPrecompiledDir()); 453 CreateDir(GetPrecompiledDir());
453} 454}
454 455
455std::string ShaderDiskCacheOpenGL::GetTransferablePath() const { 456std::string ShaderDiskCacheOpenGL::GetTransferablePath() const {
456 return FileUtil::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); 457 return Common::FS::SanitizePath(GetTransferableDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
457} 458}
458 459
459std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const { 460std::string ShaderDiskCacheOpenGL::GetPrecompiledPath() const {
460 return FileUtil::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin"); 461 return Common::FS::SanitizePath(GetPrecompiledDir() + DIR_SEP_CHR + GetTitleID() + ".bin");
461} 462}
462 463
463std::string ShaderDiskCacheOpenGL::GetTransferableDir() const { 464std::string ShaderDiskCacheOpenGL::GetTransferableDir() const {
@@ -469,7 +470,7 @@ std::string ShaderDiskCacheOpenGL::GetPrecompiledDir() const {
469} 470}
470 471
471std::string ShaderDiskCacheOpenGL::GetBaseDir() const { 472std::string ShaderDiskCacheOpenGL::GetBaseDir() const {
472 return FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir) + DIR_SEP "opengl"; 473 return Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir) + DIR_SEP "opengl";
473} 474}
474 475
475std::string ShaderDiskCacheOpenGL::GetTitleID() const { 476std::string ShaderDiskCacheOpenGL::GetTitleID() const {
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index a79cef0e9..db2bb73bc 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -25,7 +25,7 @@ namespace Core {
25class System; 25class System;
26} 26}
27 27
28namespace FileUtil { 28namespace Common::FS {
29class IOFile; 29class IOFile;
30} 30}
31 31
@@ -38,9 +38,9 @@ struct ShaderDiskCacheEntry {
38 ShaderDiskCacheEntry(); 38 ShaderDiskCacheEntry();
39 ~ShaderDiskCacheEntry(); 39 ~ShaderDiskCacheEntry();
40 40
41 bool Load(FileUtil::IOFile& file); 41 bool Load(Common::FS::IOFile& file);
42 42
43 bool Save(FileUtil::IOFile& file) const; 43 bool Save(Common::FS::IOFile& file) const;
44 44
45 bool HasProgramA() const { 45 bool HasProgramA() const {
46 return !code.empty() && !code_b.empty(); 46 return !code.empty() && !code_b.empty();
@@ -97,10 +97,10 @@ public:
97private: 97private:
98 /// Loads the transferable cache. Returns empty on failure. 98 /// Loads the transferable cache. Returns empty on failure.
99 std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile( 99 std::optional<std::vector<ShaderDiskCachePrecompiled>> LoadPrecompiledFile(
100 FileUtil::IOFile& file); 100 Common::FS::IOFile& file);
101 101
102 /// Opens current game's transferable file and write it's header if it doesn't exist 102 /// Opens current game's transferable file and write it's header if it doesn't exist
103 FileUtil::IOFile AppendTransferableFile() const; 103 Common::FS::IOFile AppendTransferableFile() const;
104 104
105 /// Save precompiled header to precompiled_cache_in_memory 105 /// Save precompiled header to precompiled_cache_in_memory
106 void SavePrecompiledHeaderToVirtualPrecompiledCache(); 106 void SavePrecompiledHeaderToVirtualPrecompiledCache();
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 0a7bc9e2b..f403f388a 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -403,7 +403,7 @@ void CachedSurface::DecorateSurfaceName() {
403 LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName()); 403 LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName());
404} 404}
405 405
406void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) { 406void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) {
407 LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix); 407 LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix);
408} 408}
409 409
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index bfc4ddf5d..de8f18489 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -90,7 +90,7 @@ public:
90 Tegra::Texture::SwizzleSource z_source, 90 Tegra::Texture::SwizzleSource z_source,
91 Tegra::Texture::SwizzleSource w_source); 91 Tegra::Texture::SwizzleSource w_source);
92 92
93 void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); 93 void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix);
94 94
95 void MarkAsModified(u64 tick) { 95 void MarkAsModified(u64 tick) {
96 surface.MarkAsModified(true, tick); 96 surface.MarkAsModified(true, tick);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 69127fdbb..c39663db7 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -510,9 +510,10 @@ void RendererOpenGL::AddTelemetryFields() {
510 LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model); 510 LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
511 511
512 auto& telemetry_session = system.TelemetrySession(); 512 auto& telemetry_session = system.TelemetrySession();
513 telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor); 513 constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
514 telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model); 514 telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor);
515 telemetry_session.AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version); 515 telemetry_session.AddField(user_system, "GPU_Model", gpu_model);
516 telemetry_session.AddField(user_system, "GPU_OpenGL_Version", gl_version);
516} 517}
517 518
518void RendererOpenGL::CreateRasterizer() { 519void RendererOpenGL::CreateRasterizer() {
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
index 435c8c1b8..5b01020ec 100644
--- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
+++ b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
@@ -65,10 +65,10 @@ bool NsightAftermathTracker::Initialize() {
65 return false; 65 return false;
66 } 66 }
67 67
68 dump_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "gpucrash"; 68 dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash";
69 69
70 (void)FileUtil::DeleteDirRecursively(dump_dir); 70 (void)Common::FS::DeleteDirRecursively(dump_dir);
71 if (!FileUtil::CreateDir(dump_dir)) { 71 if (!Common::FS::CreateDir(dump_dir)) {
72 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory"); 72 LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
73 return false; 73 return false;
74 } 74 }
@@ -106,7 +106,7 @@ void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
106 return; 106 return;
107 } 107 }
108 108
109 FileUtil::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb"); 109 Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
110 if (!file.IsOpen()) { 110 if (!file.IsOpen()) {
111 LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash); 111 LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
112 return; 112 return;
@@ -156,12 +156,12 @@ void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
156 }(); 156 }();
157 157
158 std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size); 158 std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
159 if (FileUtil::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) { 159 if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
160 LOG_ERROR(Render_Vulkan, "Failed to write dump file"); 160 LOG_ERROR(Render_Vulkan, "Failed to write dump file");
161 return; 161 return;
162 } 162 }
163 const std::string_view json_view(json.data(), json.size()); 163 const std::string_view json_view(json.data(), json.size());
164 if (FileUtil::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) { 164 if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
165 LOG_ERROR(Render_Vulkan, "Failed to write JSON"); 165 LOG_ERROR(Render_Vulkan, "Failed to write JSON");
166 return; 166 return;
167 } 167 }
@@ -180,7 +180,7 @@ void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_
180 180
181 const std::string path = 181 const std::string path =
182 fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]); 182 fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
183 FileUtil::IOFile file(path, "wb"); 183 Common::FS::IOFile file(path, "wb");
184 if (!file.IsOpen()) { 184 if (!file.IsOpen()) {
185 LOG_ERROR(Render_Vulkan, "Failed to create file {}", path); 185 LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
186 return; 186 return;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 110599f7a..ae46e0444 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -78,7 +78,7 @@ Common::DynamicLibrary OpenVulkanLibrary() {
78 if (!libvulkan_env || !library.Open(libvulkan_env)) { 78 if (!libvulkan_env || !library.Open(libvulkan_env)) {
79 // Use the libvulkan.dylib from the application bundle. 79 // Use the libvulkan.dylib from the application bundle.
80 const std::string filename = 80 const std::string filename =
81 FileUtil::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib"; 81 Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
82 library.Open(filename.c_str()); 82 library.Open(filename.c_str());
83 } 83 }
84#else 84#else
@@ -441,7 +441,7 @@ void RendererVulkan::Report() const {
441 LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version); 441 LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
442 442
443 auto& telemetry_session = system.TelemetrySession(); 443 auto& telemetry_session = system.TelemetrySession();
444 constexpr auto field = Telemetry::FieldType::UserSystem; 444 constexpr auto field = Common::Telemetry::FieldType::UserSystem;
445 telemetry_session.AddField(field, "GPU_Vendor", vendor_name); 445 telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
446 telemetry_session.AddField(field, "GPU_Model", model_name); 446 telemetry_session.AddField(field, "GPU_Model", model_name);
447 telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name); 447 telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 0c03e4d83..ebcfaa0e3 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -382,6 +382,8 @@ bool VKDevice::Create() {
382 382
383 graphics_queue = logical.GetQueue(graphics_family); 383 graphics_queue = logical.GetQueue(graphics_family);
384 present_queue = logical.GetQueue(present_family); 384 present_queue = logical.GetQueue(present_family);
385
386 use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
385 return true; 387 return true;
386} 388}
387 389
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 529744f2d..26a233db1 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -202,6 +202,11 @@ public:
202 return reported_extensions; 202 return reported_extensions;
203 } 203 }
204 204
205 /// Returns true if the setting for async shader compilation is enabled.
206 bool UseAsynchronousShaders() const {
207 return use_asynchronous_shaders;
208 }
209
205 /// Checks if the physical device is suitable. 210 /// Checks if the physical device is suitable.
206 static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface); 211 static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface);
207 212
@@ -252,6 +257,9 @@ private:
252 bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. 257 bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
253 bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. 258 bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
254 259
260 // Asynchronous Graphics Pipeline setting
261 bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline
262
255 // Telemetry parameters 263 // Telemetry parameters
256 std::string vendor_name; ///< Device's driver name. 264 std::string vendor_name; ///< Device's driver name.
257 std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions. 265 std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions.
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index a02be5487..d7f65d435 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -29,7 +29,7 @@ void InnerFence::Queue() {
29 } 29 }
30 ASSERT(!event); 30 ASSERT(!event);
31 31
32 event = device.GetLogical().CreateEvent(); 32 event = device.GetLogical().CreateNewEvent();
33 ticks = scheduler.Ticks(); 33 ticks = scheduler.Ticks();
34 34
35 scheduler.RequestOutsideRenderPassOperationContext(); 35 scheduler.RequestOutsideRenderPassOperationContext();
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index aaf930b90..2e46c6278 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -78,15 +78,14 @@ VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& sche
78 const GraphicsPipelineCacheKey& key, 78 const GraphicsPipelineCacheKey& key,
79 vk::Span<VkDescriptorSetLayoutBinding> bindings, 79 vk::Span<VkDescriptorSetLayoutBinding> bindings,
80 const SPIRVProgram& program) 80 const SPIRVProgram& program)
81 : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()}, 81 : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()},
82 descriptor_set_layout{CreateDescriptorSetLayout(bindings)}, 82 descriptor_set_layout{CreateDescriptorSetLayout(bindings)},
83 descriptor_allocator{descriptor_pool, *descriptor_set_layout}, 83 descriptor_allocator{descriptor_pool, *descriptor_set_layout},
84 update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, 84 update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()},
85 descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules( 85 descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules(
86 program)}, 86 program)},
87 renderpass{renderpass_cache.GetRenderPass(key.renderpass_params)}, pipeline{CreatePipeline( 87 renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)},
88 key.renderpass_params, 88 pipeline{CreatePipeline(cache_key.renderpass_params, program)} {}
89 program)} {}
90 89
91VKGraphicsPipeline::~VKGraphicsPipeline() = default; 90VKGraphicsPipeline::~VKGraphicsPipeline() = default;
92 91
@@ -181,7 +180,7 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
181 180
182vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, 181vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
183 const SPIRVProgram& program) const { 182 const SPIRVProgram& program) const {
184 const auto& state = fixed_state; 183 const auto& state = cache_key.fixed_state;
185 const auto& viewport_swizzles = state.viewport_swizzles; 184 const auto& viewport_swizzles = state.viewport_swizzles;
186 185
187 FixedPipelineState::DynamicState dynamic; 186 FixedPipelineState::DynamicState dynamic;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index a1d699a6c..58aa35efd 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -19,7 +19,27 @@ namespace Vulkan {
19 19
20using Maxwell = Tegra::Engines::Maxwell3D::Regs; 20using Maxwell = Tegra::Engines::Maxwell3D::Regs;
21 21
22struct GraphicsPipelineCacheKey; 22struct GraphicsPipelineCacheKey {
23 RenderPassParams renderpass_params;
24 u32 padding;
25 std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
26 FixedPipelineState fixed_state;
27
28 std::size_t Hash() const noexcept;
29
30 bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
31
32 bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
33 return !operator==(rhs);
34 }
35
36 std::size_t Size() const noexcept {
37 return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
38 }
39};
40static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
41static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
42static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
23 43
24class VKDescriptorPool; 44class VKDescriptorPool;
25class VKDevice; 45class VKDevice;
@@ -54,6 +74,10 @@ public:
54 return renderpass; 74 return renderpass;
55 } 75 }
56 76
77 GraphicsPipelineCacheKey GetCacheKey() const {
78 return cache_key;
79 }
80
57private: 81private:
58 vk::DescriptorSetLayout CreateDescriptorSetLayout( 82 vk::DescriptorSetLayout CreateDescriptorSetLayout(
59 vk::Span<VkDescriptorSetLayoutBinding> bindings) const; 83 vk::Span<VkDescriptorSetLayoutBinding> bindings) const;
@@ -70,7 +94,7 @@ private:
70 94
71 const VKDevice& device; 95 const VKDevice& device;
72 VKScheduler& scheduler; 96 VKScheduler& scheduler;
73 const FixedPipelineState fixed_state; 97 const GraphicsPipelineCacheKey cache_key;
74 const u64 hash; 98 const u64 hash;
75 99
76 vk::DescriptorSetLayout descriptor_set_layout; 100 vk::DescriptorSetLayout descriptor_set_layout;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 418c62bc4..cfdcdd6ab 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -28,6 +28,7 @@
28#include "video_core/shader/compiler_settings.h" 28#include "video_core/shader/compiler_settings.h"
29#include "video_core/shader/memory_util.h" 29#include "video_core/shader/memory_util.h"
30#include "video_core/shader_cache.h" 30#include "video_core/shader_cache.h"
31#include "video_core/shader_notify.h"
31 32
32namespace Vulkan { 33namespace Vulkan {
33 34
@@ -205,24 +206,43 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
205 return last_shaders = shaders; 206 return last_shaders = shaders;
206} 207}
207 208
208VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) { 209VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
210 const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) {
209 MICROPROFILE_SCOPE(Vulkan_PipelineCache); 211 MICROPROFILE_SCOPE(Vulkan_PipelineCache);
210 212
211 if (last_graphics_pipeline && last_graphics_key == key) { 213 if (last_graphics_pipeline && last_graphics_key == key) {
212 return *last_graphics_pipeline; 214 return last_graphics_pipeline;
213 } 215 }
214 last_graphics_key = key; 216 last_graphics_key = key;
215 217
218 if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(system.GPU())) {
219 std::unique_lock lock{pipeline_cache};
220 const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
221 if (is_cache_miss) {
222 system.GPU().ShaderNotify().MarkSharderBuilding();
223 LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
224 const auto [program, bindings] = DecompileShaders(key.fixed_state);
225 async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool,
226 update_descriptor_queue, renderpass_cache, bindings,
227 program, key);
228 }
229 last_graphics_pipeline = pair->second.get();
230 return last_graphics_pipeline;
231 }
232
216 const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); 233 const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
217 auto& entry = pair->second; 234 auto& entry = pair->second;
218 if (is_cache_miss) { 235 if (is_cache_miss) {
236 system.GPU().ShaderNotify().MarkSharderBuilding();
219 LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); 237 LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
220 const auto [program, bindings] = DecompileShaders(key); 238 const auto [program, bindings] = DecompileShaders(key.fixed_state);
221 entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool, 239 entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool,
222 update_descriptor_queue, renderpass_cache, key, 240 update_descriptor_queue, renderpass_cache, key,
223 bindings, program); 241 bindings, program);
242 system.GPU().ShaderNotify().MarkShaderComplete();
224 } 243 }
225 return *(last_graphics_pipeline = entry.get()); 244 last_graphics_pipeline = entry.get();
245 return last_graphics_pipeline;
226} 246}
227 247
228VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) { 248VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) {
@@ -277,6 +297,12 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
277 return *entry; 297 return *entry;
278} 298}
279 299
300void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) {
301 system.GPU().ShaderNotify().MarkShaderComplete();
302 std::unique_lock lock{pipeline_cache};
303 graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline);
304}
305
280void VKPipelineCache::OnShaderRemoval(Shader* shader) { 306void VKPipelineCache::OnShaderRemoval(Shader* shader) {
281 bool finished = false; 307 bool finished = false;
282 const auto Finish = [&] { 308 const auto Finish = [&] {
@@ -312,8 +338,7 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) {
312} 338}
313 339
314std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> 340std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>>
315VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { 341VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
316 const auto& fixed_state = key.fixed_state;
317 auto& memory_manager = system.GPU().MemoryManager(); 342 auto& memory_manager = system.GPU().MemoryManager();
318 const auto& gpu = system.GPU().Maxwell3D(); 343 const auto& gpu = system.GPU().Maxwell3D();
319 344
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 0a3fe65fb..c04829e77 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -22,6 +22,7 @@
22#include "video_core/renderer_vulkan/vk_renderpass_cache.h" 22#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
23#include "video_core/renderer_vulkan/vk_shader_decompiler.h" 23#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
24#include "video_core/renderer_vulkan/wrapper.h" 24#include "video_core/renderer_vulkan/wrapper.h"
25#include "video_core/shader/async_shaders.h"
25#include "video_core/shader/memory_util.h" 26#include "video_core/shader/memory_util.h"
26#include "video_core/shader/registry.h" 27#include "video_core/shader/registry.h"
27#include "video_core/shader/shader_ir.h" 28#include "video_core/shader/shader_ir.h"
@@ -43,28 +44,6 @@ class VKUpdateDescriptorQueue;
43 44
44using Maxwell = Tegra::Engines::Maxwell3D::Regs; 45using Maxwell = Tegra::Engines::Maxwell3D::Regs;
45 46
46struct GraphicsPipelineCacheKey {
47 RenderPassParams renderpass_params;
48 u32 padding;
49 std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
50 FixedPipelineState fixed_state;
51
52 std::size_t Hash() const noexcept;
53
54 bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept;
55
56 bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
57 return !operator==(rhs);
58 }
59
60 std::size_t Size() const noexcept {
61 return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
62 }
63};
64static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
65static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
66static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
67
68struct ComputePipelineCacheKey { 47struct ComputePipelineCacheKey {
69 GPUVAddr shader; 48 GPUVAddr shader;
70 u32 shared_memory_size; 49 u32 shared_memory_size;
@@ -152,16 +131,19 @@ public:
152 131
153 std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders(); 132 std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders();
154 133
155 VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key); 134 VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key,
135 VideoCommon::Shader::AsyncShaders& async_shaders);
156 136
157 VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); 137 VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key);
158 138
139 void EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline);
140
159protected: 141protected:
160 void OnShaderRemoval(Shader* shader) final; 142 void OnShaderRemoval(Shader* shader) final;
161 143
162private: 144private:
163 std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders( 145 std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders(
164 const GraphicsPipelineCacheKey& key); 146 const FixedPipelineState& fixed_state);
165 147
166 Core::System& system; 148 Core::System& system;
167 const VKDevice& device; 149 const VKDevice& device;
@@ -178,6 +160,7 @@ private:
178 GraphicsPipelineCacheKey last_graphics_key; 160 GraphicsPipelineCacheKey last_graphics_key;
179 VKGraphicsPipeline* last_graphics_pipeline = nullptr; 161 VKGraphicsPipeline* last_graphics_pipeline = nullptr;
180 162
163 std::mutex pipeline_cache;
181 std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>> 164 std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>>
182 graphics_cache; 165 graphics_cache;
183 std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache; 166 std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 7500e8244..936f76195 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -14,6 +14,7 @@
14#include "common/assert.h" 14#include "common/assert.h"
15#include "common/logging/log.h" 15#include "common/logging/log.h"
16#include "common/microprofile.h" 16#include "common/microprofile.h"
17#include "common/scope_exit.h"
17#include "core/core.h" 18#include "core/core.h"
18#include "core/settings.h" 19#include "core/settings.h"
19#include "video_core/engines/kepler_compute.h" 20#include "video_core/engines/kepler_compute.h"
@@ -400,8 +401,12 @@ RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWind
400 buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), 401 buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
401 sampler_cache(device), 402 sampler_cache(device),
402 fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache), 403 fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache),
403 query_cache(system, *this, device, scheduler), wfi_event{device.GetLogical().CreateEvent()} { 404 query_cache(system, *this, device, scheduler),
405 wfi_event{device.GetLogical().CreateNewEvent()}, async_shaders{renderer} {
404 scheduler.SetQueryCache(query_cache); 406 scheduler.SetQueryCache(query_cache);
407 if (device.UseAsynchronousShaders()) {
408 async_shaders.AllocateWorkers();
409 }
405} 410}
406 411
407RasterizerVulkan::~RasterizerVulkan() = default; 412RasterizerVulkan::~RasterizerVulkan() = default;
@@ -413,6 +418,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
413 418
414 query_cache.UpdateCounters(); 419 query_cache.UpdateCounters();
415 420
421 SCOPE_EXIT({ system.GPU().TickWork(); });
422
416 const auto& gpu = system.GPU().Maxwell3D(); 423 const auto& gpu = system.GPU().Maxwell3D();
417 GraphicsPipelineCacheKey key; 424 GraphicsPipelineCacheKey key;
418 key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); 425 key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
@@ -439,10 +446,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
439 key.renderpass_params = GetRenderPassParams(texceptions); 446 key.renderpass_params = GetRenderPassParams(texceptions);
440 key.padding = 0; 447 key.padding = 0;
441 448
442 auto& pipeline = pipeline_cache.GetGraphicsPipeline(key); 449 auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders);
443 scheduler.BindGraphicsPipeline(pipeline.GetHandle()); 450 if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {
451 // Async graphics pipeline was not ready.
452 return;
453 }
454
455 scheduler.BindGraphicsPipeline(pipeline->GetHandle());
444 456
445 const auto renderpass = pipeline.GetRenderPass(); 457 const auto renderpass = pipeline->GetRenderPass();
446 const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); 458 const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
447 scheduler.RequestRenderpass(renderpass, framebuffer, render_area); 459 scheduler.RequestRenderpass(renderpass, framebuffer, render_area);
448 460
@@ -452,8 +464,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
452 464
453 BeginTransformFeedback(); 465 BeginTransformFeedback();
454 466
455 const auto pipeline_layout = pipeline.GetLayout(); 467 const auto pipeline_layout = pipeline->GetLayout();
456 const auto descriptor_set = pipeline.CommitDescriptorSet(); 468 const auto descriptor_set = pipeline->CommitDescriptorSet();
457 scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) { 469 scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) {
458 if (descriptor_set) { 470 if (descriptor_set) {
459 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 471 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
@@ -463,8 +475,6 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
463 }); 475 });
464 476
465 EndTransformFeedback(); 477 EndTransformFeedback();
466
467 system.GPU().TickWork();
468} 478}
469 479
470void RasterizerVulkan::Clear() { 480void RasterizerVulkan::Clear() {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 923178b0b..f640ba649 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -32,6 +32,7 @@
32#include "video_core/renderer_vulkan/vk_texture_cache.h" 32#include "video_core/renderer_vulkan/vk_texture_cache.h"
33#include "video_core/renderer_vulkan/vk_update_descriptor.h" 33#include "video_core/renderer_vulkan/vk_update_descriptor.h"
34#include "video_core/renderer_vulkan/wrapper.h" 34#include "video_core/renderer_vulkan/wrapper.h"
35#include "video_core/shader/async_shaders.h"
35 36
36namespace Core { 37namespace Core {
37class System; 38class System;
@@ -136,6 +137,14 @@ public:
136 u32 pixel_stride) override; 137 u32 pixel_stride) override;
137 void SetupDirtyFlags() override; 138 void SetupDirtyFlags() override;
138 139
140 VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
141 return async_shaders;
142 }
143
144 const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const {
145 return async_shaders;
146 }
147
139 /// Maximum supported size that a constbuffer can have in bytes. 148 /// Maximum supported size that a constbuffer can have in bytes.
140 static constexpr std::size_t MaxConstbufferSize = 0x10000; 149 static constexpr std::size_t MaxConstbufferSize = 0x10000;
141 static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0, 150 static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0,
@@ -297,6 +306,7 @@ private:
297 vk::Buffer default_buffer; 306 vk::Buffer default_buffer;
298 VKMemoryCommit default_buffer_commit; 307 VKMemoryCommit default_buffer_commit;
299 vk::Event wfi_event; 308 vk::Event wfi_event;
309 VideoCommon::Shader::AsyncShaders async_shaders;
300 310
301 std::array<View, Maxwell::NumRenderTargets> color_attachments; 311 std::array<View, Maxwell::NumRenderTargets> color_attachments;
302 View zeta_attachment; 312 View zeta_attachment;
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index 14cac38ea..013865aa4 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -644,7 +644,7 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons
644 return ShaderModule(object, handle, *dld); 644 return ShaderModule(object, handle, *dld);
645} 645}
646 646
647Event Device::CreateEvent() const { 647Event Device::CreateNewEvent() const {
648 static constexpr VkEventCreateInfo ci{ 648 static constexpr VkEventCreateInfo ci{
649 .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, 649 .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
650 .pNext = nullptr, 650 .pNext = nullptr,
@@ -786,7 +786,7 @@ std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProp
786 VK_SUCCESS) { 786 VK_SUCCESS) {
787 return std::nullopt; 787 return std::nullopt;
788 } 788 }
789 return properties; 789 return std::move(properties);
790} 790}
791 791
792std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties( 792std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
index 31885ef42..b9d3fedc1 100644
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ b/src/video_core/renderer_vulkan/wrapper.h
@@ -721,7 +721,7 @@ public:
721 721
722 ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; 722 ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
723 723
724 Event CreateEvent() const; 724 Event CreateNewEvent() const;
725 725
726 SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; 726 SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
727 727
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
index b7f66d7ee..f815584f7 100644
--- a/src/video_core/shader/async_shaders.cpp
+++ b/src/video_core/shader/async_shaders.cpp
@@ -2,7 +2,6 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <chrono>
6#include <condition_variable> 5#include <condition_variable>
7#include <mutex> 6#include <mutex>
8#include <thread> 7#include <thread>
@@ -20,9 +19,18 @@ AsyncShaders::~AsyncShaders() {
20 KillWorkers(); 19 KillWorkers();
21} 20}
22 21
23void AsyncShaders::AllocateWorkers(std::size_t num_workers) { 22void AsyncShaders::AllocateWorkers() {
24 // If we're already have workers queued or don't want to queue workers, ignore 23 // Max worker threads we should allow
25 if (num_workers == worker_threads.size() || num_workers == 0) { 24 constexpr u32 MAX_THREADS = 4;
25 // Deduce how many threads we can use
26 const u32 threads_used = std::thread::hardware_concurrency() / 4;
27 // Always allow at least 1 thread regardless of our settings
28 const auto max_worker_count = std::max(1U, threads_used);
29 // Don't use more than MAX_THREADS
30 const auto num_workers = std::min(max_worker_count, MAX_THREADS);
31
32 // If we already have workers queued, ignore
33 if (num_workers == worker_threads.size()) {
26 return; 34 return;
27 } 35 }
28 36
@@ -34,8 +42,8 @@ void AsyncShaders::AllocateWorkers(std::size_t num_workers) {
34 // Create workers 42 // Create workers
35 for (std::size_t i = 0; i < num_workers; i++) { 43 for (std::size_t i = 0; i < num_workers; i++) {
36 context_list.push_back(emu_window.CreateSharedContext()); 44 context_list.push_back(emu_window.CreateSharedContext());
37 worker_threads.push_back(std::move( 45 worker_threads.push_back(
38 std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get()))); 46 std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get()));
39 } 47 }
40} 48}
41 49
@@ -111,24 +119,50 @@ void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
111 VideoCommon::Shader::CompilerSettings compiler_settings, 119 VideoCommon::Shader::CompilerSettings compiler_settings,
112 const VideoCommon::Shader::Registry& registry, 120 const VideoCommon::Shader::Registry& registry,
113 VAddr cpu_addr) { 121 VAddr cpu_addr) {
114 WorkerParams params{device.UseAssemblyShaders() ? AsyncShaders::Backend::GLASM 122 WorkerParams params{
115 : AsyncShaders::Backend::OpenGL, 123 .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL,
116 device, 124 .device = &device,
117 shader_type, 125 .shader_type = shader_type,
118 uid, 126 .uid = uid,
119 std::move(code), 127 .code = std::move(code),
120 std::move(code_b), 128 .code_b = std::move(code_b),
121 main_offset, 129 .main_offset = main_offset,
122 compiler_settings, 130 .compiler_settings = compiler_settings,
123 registry, 131 .registry = registry,
124 cpu_addr}; 132 .cpu_address = cpu_addr,
133 };
125 std::unique_lock lock(queue_mutex); 134 std::unique_lock lock(queue_mutex);
126 pending_queue.push_back(std::move(params)); 135 pending_queue.push(std::move(params));
136 cv.notify_one();
137}
138
139void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
140 const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler,
141 Vulkan::VKDescriptorPool& descriptor_pool,
142 Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
143 Vulkan::VKRenderPassCache& renderpass_cache,
144 std::vector<VkDescriptorSetLayoutBinding> bindings,
145 Vulkan::SPIRVProgram program,
146 Vulkan::GraphicsPipelineCacheKey key) {
147 WorkerParams params{
148 .backend = Backend::Vulkan,
149 .pp_cache = pp_cache,
150 .vk_device = &device,
151 .scheduler = &scheduler,
152 .descriptor_pool = &descriptor_pool,
153 .update_descriptor_queue = &update_descriptor_queue,
154 .renderpass_cache = &renderpass_cache,
155 .bindings = bindings,
156 .program = program,
157 .key = key,
158 };
159
160 std::unique_lock lock(queue_mutex);
161 pending_queue.push(std::move(params));
127 cv.notify_one(); 162 cv.notify_one();
128} 163}
129 164
130void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) { 165void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) {
131 using namespace std::chrono_literals;
132 while (!is_thread_exiting.load(std::memory_order_relaxed)) { 166 while (!is_thread_exiting.load(std::memory_order_relaxed)) {
133 std::unique_lock lock{queue_mutex}; 167 std::unique_lock lock{queue_mutex};
134 cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; }); 168 cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; });
@@ -144,18 +178,17 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
144 if (pending_queue.empty()) { 178 if (pending_queue.empty()) {
145 continue; 179 continue;
146 } 180 }
181
147 // Pull work from queue 182 // Pull work from queue
148 WorkerParams work = std::move(pending_queue.front()); 183 WorkerParams work = std::move(pending_queue.front());
149 pending_queue.pop_front(); 184 pending_queue.pop();
150
151 lock.unlock(); 185 lock.unlock();
152 186
153 if (work.backend == AsyncShaders::Backend::OpenGL || 187 if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) {
154 work.backend == AsyncShaders::Backend::GLASM) { 188 const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry);
155 const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, work.registry);
156 const auto scope = context->Acquire(); 189 const auto scope = context->Acquire();
157 auto program = 190 auto program =
158 OpenGL::BuildShader(work.device, work.shader_type, work.uid, ir, work.registry); 191 OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry);
159 Result result{}; 192 Result result{};
160 result.backend = work.backend; 193 result.backend = work.backend;
161 result.cpu_address = work.cpu_address; 194 result.cpu_address = work.cpu_address;
@@ -164,9 +197,9 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
164 result.code_b = std::move(work.code_b); 197 result.code_b = std::move(work.code_b);
165 result.shader_type = work.shader_type; 198 result.shader_type = work.shader_type;
166 199
167 if (work.backend == AsyncShaders::Backend::OpenGL) { 200 if (work.backend == Backend::OpenGL) {
168 result.program.opengl = std::move(program->source_program); 201 result.program.opengl = std::move(program->source_program);
169 } else if (work.backend == AsyncShaders::Backend::GLASM) { 202 } else if (work.backend == Backend::GLASM) {
170 result.program.glasm = std::move(program->assembly_program); 203 result.program.glasm = std::move(program->assembly_program);
171 } 204 }
172 205
@@ -174,6 +207,13 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
174 std::unique_lock complete_lock(completed_mutex); 207 std::unique_lock complete_lock(completed_mutex);
175 finished_work.push_back(std::move(result)); 208 finished_work.push_back(std::move(result));
176 } 209 }
210 } else if (work.backend == Backend::Vulkan) {
211 auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
212 *work.vk_device, *work.scheduler, *work.descriptor_pool,
213 *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings,
214 work.program);
215
216 work.pp_cache->EmplacePipeline(std::move(pipeline));
177 } 217 }
178 } 218 }
179} 219}
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
index 2f5ee94ad..d5ae814d5 100644
--- a/src/video_core/shader/async_shaders.h
+++ b/src/video_core/shader/async_shaders.h
@@ -14,6 +14,10 @@
14#include "video_core/renderer_opengl/gl_device.h" 14#include "video_core/renderer_opengl/gl_device.h"
15#include "video_core/renderer_opengl/gl_resource_manager.h" 15#include "video_core/renderer_opengl/gl_resource_manager.h"
16#include "video_core/renderer_opengl/gl_shader_decompiler.h" 16#include "video_core/renderer_opengl/gl_shader_decompiler.h"
17#include "video_core/renderer_vulkan/vk_device.h"
18#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
19#include "video_core/renderer_vulkan/vk_scheduler.h"
20#include "video_core/renderer_vulkan/vk_update_descriptor.h"
17 21
18namespace Core::Frontend { 22namespace Core::Frontend {
19class EmuWindow; 23class EmuWindow;
@@ -24,6 +28,10 @@ namespace Tegra {
24class GPU; 28class GPU;
25} 29}
26 30
31namespace Vulkan {
32class VKPipelineCache;
33}
34
27namespace VideoCommon::Shader { 35namespace VideoCommon::Shader {
28 36
29class AsyncShaders { 37class AsyncShaders {
@@ -31,6 +39,7 @@ public:
31 enum class Backend { 39 enum class Backend {
32 OpenGL, 40 OpenGL,
33 GLASM, 41 GLASM,
42 Vulkan,
34 }; 43 };
35 44
36 struct ResultPrograms { 45 struct ResultPrograms {
@@ -52,7 +61,7 @@ public:
52 ~AsyncShaders(); 61 ~AsyncShaders();
53 62
54 /// Start up shader worker threads 63 /// Start up shader worker threads
55 void AllocateWorkers(std::size_t num_workers); 64 void AllocateWorkers();
56 65
57 /// Clear the shader queue and kill all worker threads 66 /// Clear the shader queue and kill all worker threads
58 void FreeWorkers(); 67 void FreeWorkers();
@@ -76,6 +85,14 @@ public:
76 VideoCommon::Shader::CompilerSettings compiler_settings, 85 VideoCommon::Shader::CompilerSettings compiler_settings,
77 const VideoCommon::Shader::Registry& registry, VAddr cpu_addr); 86 const VideoCommon::Shader::Registry& registry, VAddr cpu_addr);
78 87
88 void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device,
89 Vulkan::VKScheduler& scheduler,
90 Vulkan::VKDescriptorPool& descriptor_pool,
91 Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
92 Vulkan::VKRenderPassCache& renderpass_cache,
93 std::vector<VkDescriptorSetLayoutBinding> bindings,
94 Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key);
95
79private: 96private:
80 void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); 97 void ShaderCompilerThread(Core::Frontend::GraphicsContext* context);
81 98
@@ -83,16 +100,28 @@ private:
83 bool HasWorkQueued(); 100 bool HasWorkQueued();
84 101
85 struct WorkerParams { 102 struct WorkerParams {
86 AsyncShaders::Backend backend; 103 Backend backend;
87 OpenGL::Device device; 104 // For OGL
105 const OpenGL::Device* device;
88 Tegra::Engines::ShaderType shader_type; 106 Tegra::Engines::ShaderType shader_type;
89 u64 uid; 107 u64 uid;
90 std::vector<u64> code; 108 std::vector<u64> code;
91 std::vector<u64> code_b; 109 std::vector<u64> code_b;
92 u32 main_offset; 110 u32 main_offset;
93 VideoCommon::Shader::CompilerSettings compiler_settings; 111 VideoCommon::Shader::CompilerSettings compiler_settings;
94 VideoCommon::Shader::Registry registry; 112 std::optional<VideoCommon::Shader::Registry> registry;
95 VAddr cpu_address; 113 VAddr cpu_address;
114
115 // For Vulkan
116 Vulkan::VKPipelineCache* pp_cache;
117 const Vulkan::VKDevice* vk_device;
118 Vulkan::VKScheduler* scheduler;
119 Vulkan::VKDescriptorPool* descriptor_pool;
120 Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue;
121 Vulkan::VKRenderPassCache* renderpass_cache;
122 std::vector<VkDescriptorSetLayoutBinding> bindings;
123 Vulkan::SPIRVProgram program;
124 Vulkan::GraphicsPipelineCacheKey key;
96 }; 125 };
97 126
98 std::condition_variable cv; 127 std::condition_variable cv;
@@ -101,7 +130,7 @@ private:
101 std::atomic<bool> is_thread_exiting{}; 130 std::atomic<bool> is_thread_exiting{};
102 std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list; 131 std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list;
103 std::vector<std::thread> worker_threads; 132 std::vector<std::thread> worker_threads;
104 std::deque<WorkerParams> pending_queue; 133 std::queue<WorkerParams> pending_queue;
105 std::vector<AsyncShaders::Result> finished_work; 134 std::vector<AsyncShaders::Result> finished_work;
106 Core::Frontend::EmuWindow& emu_window; 135 Core::Frontend::EmuWindow& emu_window;
107}; 136};
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 06ab7c59d..7e484b906 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -5,6 +5,7 @@ add_library(web_service STATIC
5 verify_login.h 5 verify_login.h
6 web_backend.cpp 6 web_backend.cpp
7 web_backend.h 7 web_backend.h
8 web_result.h
8) 9)
9 10
10create_target_directory_groups(web_service) 11create_target_directory_groups(web_service)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 7a480e33c..6215c914f 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -4,12 +4,14 @@
4 4
5#include <nlohmann/json.hpp> 5#include <nlohmann/json.hpp>
6#include "common/detached_tasks.h" 6#include "common/detached_tasks.h"
7#include "common/web_result.h"
8#include "web_service/telemetry_json.h" 7#include "web_service/telemetry_json.h"
9#include "web_service/web_backend.h" 8#include "web_service/web_backend.h"
9#include "web_service/web_result.h"
10 10
11namespace WebService { 11namespace WebService {
12 12
13namespace Telemetry = Common::Telemetry;
14
13struct TelemetryJson::Impl { 15struct TelemetryJson::Impl {
14 Impl(std::string host, std::string username, std::string token) 16 Impl(std::string host, std::string username, std::string token)
15 : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {} 17 : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
@@ -123,7 +125,7 @@ bool TelemetryJson::SubmitTestcase() {
123 Client client(impl->host, impl->username, impl->token); 125 Client client(impl->host, impl->username, impl->token);
124 auto value = client.PostJson("/gamedb/testcase", content, false); 126 auto value = client.PostJson("/gamedb/testcase", content, false);
125 127
126 return value.result_code == Common::WebResult::Code::Success; 128 return value.result_code == WebResult::Code::Success;
127} 129}
128 130
129} // namespace WebService 131} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index dfd202829..df51e00f8 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -14,25 +14,25 @@ namespace WebService {
14 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the 14 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
15 * yuzu web service 15 * yuzu web service
16 */ 16 */
17class TelemetryJson : public Telemetry::VisitorInterface { 17class TelemetryJson : public Common::Telemetry::VisitorInterface {
18public: 18public:
19 TelemetryJson(std::string host, std::string username, std::string token); 19 TelemetryJson(std::string host, std::string username, std::string token);
20 ~TelemetryJson() override; 20 ~TelemetryJson() override;
21 21
22 void Visit(const Telemetry::Field<bool>& field) override; 22 void Visit(const Common::Telemetry::Field<bool>& field) override;
23 void Visit(const Telemetry::Field<double>& field) override; 23 void Visit(const Common::Telemetry::Field<double>& field) override;
24 void Visit(const Telemetry::Field<float>& field) override; 24 void Visit(const Common::Telemetry::Field<float>& field) override;
25 void Visit(const Telemetry::Field<u8>& field) override; 25 void Visit(const Common::Telemetry::Field<u8>& field) override;
26 void Visit(const Telemetry::Field<u16>& field) override; 26 void Visit(const Common::Telemetry::Field<u16>& field) override;
27 void Visit(const Telemetry::Field<u32>& field) override; 27 void Visit(const Common::Telemetry::Field<u32>& field) override;
28 void Visit(const Telemetry::Field<u64>& field) override; 28 void Visit(const Common::Telemetry::Field<u64>& field) override;
29 void Visit(const Telemetry::Field<s8>& field) override; 29 void Visit(const Common::Telemetry::Field<s8>& field) override;
30 void Visit(const Telemetry::Field<s16>& field) override; 30 void Visit(const Common::Telemetry::Field<s16>& field) override;
31 void Visit(const Telemetry::Field<s32>& field) override; 31 void Visit(const Common::Telemetry::Field<s32>& field) override;
32 void Visit(const Telemetry::Field<s64>& field) override; 32 void Visit(const Common::Telemetry::Field<s64>& field) override;
33 void Visit(const Telemetry::Field<std::string>& field) override; 33 void Visit(const Common::Telemetry::Field<std::string>& field) override;
34 void Visit(const Telemetry::Field<const char*>& field) override; 34 void Visit(const Common::Telemetry::Field<const char*>& field) override;
35 void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; 35 void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override;
36 36
37 void Complete() override; 37 void Complete() override;
38 bool SubmitTestcase() override; 38 bool SubmitTestcase() override;
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index bfaa5b70a..ceb55ca6b 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -3,9 +3,9 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <nlohmann/json.hpp> 5#include <nlohmann/json.hpp>
6#include "common/web_result.h"
7#include "web_service/verify_login.h" 6#include "web_service/verify_login.h"
8#include "web_service/web_backend.h" 7#include "web_service/web_backend.h"
8#include "web_service/web_result.h"
9 9
10namespace WebService { 10namespace WebService {
11 11
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 09d1651ac..74e287045 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -6,13 +6,14 @@
6#include <cstdlib> 6#include <cstdlib>
7#include <mutex> 7#include <mutex>
8#include <string> 8#include <string>
9
9#include <LUrlParser.h> 10#include <LUrlParser.h>
10#include <fmt/format.h> 11#include <fmt/format.h>
11#include <httplib.h> 12#include <httplib.h>
12#include "common/common_types.h" 13
13#include "common/logging/log.h" 14#include "common/logging/log.h"
14#include "common/web_result.h"
15#include "web_service/web_backend.h" 15#include "web_service/web_backend.h"
16#include "web_service/web_result.h"
16 17
17namespace WebService { 18namespace WebService {
18 19
@@ -33,17 +34,16 @@ struct Client::Impl {
33 } 34 }
34 35
35 /// A generic function handles POST, GET and DELETE request together 36 /// A generic function handles POST, GET and DELETE request together
36 Common::WebResult GenericRequest(const std::string& method, const std::string& path, 37 WebResult GenericRequest(const std::string& method, const std::string& path,
37 const std::string& data, bool allow_anonymous, 38 const std::string& data, bool allow_anonymous,
38 const std::string& accept) { 39 const std::string& accept) {
39 if (jwt.empty()) { 40 if (jwt.empty()) {
40 UpdateJWT(); 41 UpdateJWT();
41 } 42 }
42 43
43 if (jwt.empty() && !allow_anonymous) { 44 if (jwt.empty() && !allow_anonymous) {
44 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); 45 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
45 return Common::WebResult{Common::WebResult::Code::CredentialsMissing, 46 return WebResult{WebResult::Code::CredentialsMissing, "Credentials needed", ""};
46 "Credentials needed", ""};
47 } 47 }
48 48
49 auto result = GenericRequest(method, path, data, accept, jwt); 49 auto result = GenericRequest(method, path, data, accept, jwt);
@@ -62,10 +62,10 @@ struct Client::Impl {
62 * username + token is used if jwt is empty but username and token are 62 * username + token is used if jwt is empty but username and token are
63 * not empty anonymous if all of jwt, username and token are empty 63 * not empty anonymous if all of jwt, username and token are empty
64 */ 64 */
65 Common::WebResult GenericRequest(const std::string& method, const std::string& path, 65 WebResult GenericRequest(const std::string& method, const std::string& path,
66 const std::string& data, const std::string& accept, 66 const std::string& data, const std::string& accept,
67 const std::string& jwt = "", const std::string& username = "", 67 const std::string& jwt = "", const std::string& username = "",
68 const std::string& token = "") { 68 const std::string& token = "") {
69 if (cli == nullptr) { 69 if (cli == nullptr) {
70 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); 70 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
71 int port; 71 int port;
@@ -81,12 +81,12 @@ struct Client::Impl {
81 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port); 81 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
82 } else { 82 } else {
83 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); 83 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
84 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme", ""}; 84 return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""};
85 } 85 }
86 } 86 }
87 if (cli == nullptr) { 87 if (cli == nullptr) {
88 LOG_ERROR(WebService, "Invalid URL {}", host + path); 88 LOG_ERROR(WebService, "Invalid URL {}", host + path);
89 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL", ""}; 89 return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""};
90 } 90 }
91 cli->set_timeout_sec(TIMEOUT_SECONDS); 91 cli->set_timeout_sec(TIMEOUT_SECONDS);
92 92
@@ -106,7 +106,7 @@ struct Client::Impl {
106 std::string(API_VERSION.begin(), API_VERSION.end())); 106 std::string(API_VERSION.begin(), API_VERSION.end()));
107 if (method != "GET") { 107 if (method != "GET") {
108 params.emplace(std::string("Content-Type"), std::string("application/json")); 108 params.emplace(std::string("Content-Type"), std::string("application/json"));
109 }; 109 }
110 110
111 httplib::Request request; 111 httplib::Request request;
112 request.method = method; 112 request.method = method;
@@ -118,29 +118,28 @@ struct Client::Impl {
118 118
119 if (!cli->send(request, response)) { 119 if (!cli->send(request, response)) {
120 LOG_ERROR(WebService, "{} to {} returned null", method, host + path); 120 LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
121 return Common::WebResult{Common::WebResult::Code::LibError, "Null response", ""}; 121 return WebResult{WebResult::Code::LibError, "Null response", ""};
122 } 122 }
123 123
124 if (response.status >= 400) { 124 if (response.status >= 400) {
125 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, 125 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
126 response.status); 126 response.status);
127 return Common::WebResult{Common::WebResult::Code::HttpError, 127 return WebResult{WebResult::Code::HttpError, std::to_string(response.status), ""};
128 std::to_string(response.status), ""};
129 } 128 }
130 129
131 auto content_type = response.headers.find("content-type"); 130 auto content_type = response.headers.find("content-type");
132 131
133 if (content_type == response.headers.end()) { 132 if (content_type == response.headers.end()) {
134 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); 133 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
135 return Common::WebResult{Common::WebResult::Code::WrongContent, "", ""}; 134 return WebResult{WebResult::Code::WrongContent, "", ""};
136 } 135 }
137 136
138 if (content_type->second.find(accept) == std::string::npos) { 137 if (content_type->second.find(accept) == std::string::npos) {
139 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, 138 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
140 content_type->second); 139 content_type->second);
141 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content", ""}; 140 return WebResult{WebResult::Code::WrongContent, "Wrong content", ""};
142 } 141 }
143 return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; 142 return WebResult{WebResult::Code::Success, "", response.body};
144 } 143 }
145 144
146 // Retrieve a new JWT from given username and token 145 // Retrieve a new JWT from given username and token
@@ -150,7 +149,7 @@ struct Client::Impl {
150 } 149 }
151 150
152 auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token); 151 auto result = GenericRequest("POST", "/jwt/internal", "", "text/html", "", username, token);
153 if (result.result_code != Common::WebResult::Code::Success) { 152 if (result.result_code != WebResult::Code::Success) {
154 LOG_ERROR(WebService, "UpdateJWT failed"); 153 LOG_ERROR(WebService, "UpdateJWT failed");
155 } else { 154 } else {
156 std::lock_guard lock{jwt_cache.mutex}; 155 std::lock_guard lock{jwt_cache.mutex};
@@ -180,29 +179,28 @@ Client::Client(std::string host, std::string username, std::string token)
180 179
181Client::~Client() = default; 180Client::~Client() = default;
182 181
183Common::WebResult Client::PostJson(const std::string& path, const std::string& data, 182WebResult Client::PostJson(const std::string& path, const std::string& data, bool allow_anonymous) {
184 bool allow_anonymous) {
185 return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json"); 183 return impl->GenericRequest("POST", path, data, allow_anonymous, "application/json");
186} 184}
187 185
188Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { 186WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
189 return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json"); 187 return impl->GenericRequest("GET", path, "", allow_anonymous, "application/json");
190} 188}
191 189
192Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data, 190WebResult Client::DeleteJson(const std::string& path, const std::string& data,
193 bool allow_anonymous) { 191 bool allow_anonymous) {
194 return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json"); 192 return impl->GenericRequest("DELETE", path, data, allow_anonymous, "application/json");
195} 193}
196 194
197Common::WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) { 195WebResult Client::GetPlain(const std::string& path, bool allow_anonymous) {
198 return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain"); 196 return impl->GenericRequest("GET", path, "", allow_anonymous, "text/plain");
199} 197}
200 198
201Common::WebResult Client::GetImage(const std::string& path, bool allow_anonymous) { 199WebResult Client::GetImage(const std::string& path, bool allow_anonymous) {
202 return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png"); 200 return impl->GenericRequest("GET", path, "", allow_anonymous, "image/png");
203} 201}
204 202
205Common::WebResult Client::GetExternalJWT(const std::string& audience) { 203WebResult Client::GetExternalJWT(const std::string& audience) {
206 return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false, 204 return impl->GenericRequest("POST", fmt::format("/jwt/external/{}", audience), "", false,
207 "text/html"); 205 "text/html");
208} 206}
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 04121f17e..81f58583c 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -7,12 +7,10 @@
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9 9
10namespace Common {
11struct WebResult;
12}
13
14namespace WebService { 10namespace WebService {
15 11
12struct WebResult;
13
16class Client { 14class Client {
17public: 15public:
18 Client(std::string host, std::string username, std::string token); 16 Client(std::string host, std::string username, std::string token);
@@ -25,8 +23,7 @@ public:
25 * @param allow_anonymous If true, allow anonymous unauthenticated requests. 23 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
26 * @return the result of the request. 24 * @return the result of the request.
27 */ 25 */
28 Common::WebResult PostJson(const std::string& path, const std::string& data, 26 WebResult PostJson(const std::string& path, const std::string& data, bool allow_anonymous);
29 bool allow_anonymous);
30 27
31 /** 28 /**
32 * Gets JSON from the specified path. 29 * Gets JSON from the specified path.
@@ -34,7 +31,7 @@ public:
34 * @param allow_anonymous If true, allow anonymous unauthenticated requests. 31 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
35 * @return the result of the request. 32 * @return the result of the request.
36 */ 33 */
37 Common::WebResult GetJson(const std::string& path, bool allow_anonymous); 34 WebResult GetJson(const std::string& path, bool allow_anonymous);
38 35
39 /** 36 /**
40 * Deletes JSON to the specified path. 37 * Deletes JSON to the specified path.
@@ -43,8 +40,7 @@ public:
43 * @param allow_anonymous If true, allow anonymous unauthenticated requests. 40 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
44 * @return the result of the request. 41 * @return the result of the request.
45 */ 42 */
46 Common::WebResult DeleteJson(const std::string& path, const std::string& data, 43 WebResult DeleteJson(const std::string& path, const std::string& data, bool allow_anonymous);
47 bool allow_anonymous);
48 44
49 /** 45 /**
50 * Gets a plain string from the specified path. 46 * Gets a plain string from the specified path.
@@ -52,7 +48,7 @@ public:
52 * @param allow_anonymous If true, allow anonymous unauthenticated requests. 48 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
53 * @return the result of the request. 49 * @return the result of the request.
54 */ 50 */
55 Common::WebResult GetPlain(const std::string& path, bool allow_anonymous); 51 WebResult GetPlain(const std::string& path, bool allow_anonymous);
56 52
57 /** 53 /**
58 * Gets an PNG image from the specified path. 54 * Gets an PNG image from the specified path.
@@ -60,14 +56,14 @@ public:
60 * @param allow_anonymous If true, allow anonymous unauthenticated requests. 56 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
61 * @return the result of the request. 57 * @return the result of the request.
62 */ 58 */
63 Common::WebResult GetImage(const std::string& path, bool allow_anonymous); 59 WebResult GetImage(const std::string& path, bool allow_anonymous);
64 60
65 /** 61 /**
66 * Requests an external JWT for the specific audience provided. 62 * Requests an external JWT for the specific audience provided.
67 * @param audience the audience of the JWT requested. 63 * @param audience the audience of the JWT requested.
68 * @return the result of the request. 64 * @return the result of the request.
69 */ 65 */
70 Common::WebResult GetExternalJWT(const std::string& audience); 66 WebResult GetExternalJWT(const std::string& audience);
71 67
72private: 68private:
73 struct Impl; 69 struct Impl;
diff --git a/src/common/web_result.h b/src/web_service/web_result.h
index 8bfa2141d..3aeeb5288 100644
--- a/src/common/web_result.h
+++ b/src/web_service/web_result.h
@@ -7,7 +7,7 @@
7#include <string> 7#include <string>
8#include "common/common_types.h" 8#include "common/common_types.h"
9 9
10namespace Common { 10namespace WebService {
11struct WebResult { 11struct WebResult {
12 enum class Code : u32 { 12 enum class Code : u32 {
13 Success, 13 Success,
@@ -22,4 +22,4 @@ struct WebResult {
22 std::string result_string; 22 std::string result_string;
23 std::string returned_data; 23 std::string returned_data;
24}; 24};
25} // namespace Common 25} // namespace WebService
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index 4bc8ee726..dca8835ed 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -26,7 +26,7 @@ QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
26} 26}
27 27
28QString GetImagePath(Common::UUID uuid) { 28QString GetImagePath(Common::UUID uuid) {
29 const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 29 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
30 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 30 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
31 return QString::fromStdString(path); 31 return QString::fromStdString(path);
32} 32}
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 5477f050c..649912557 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -54,7 +54,8 @@ void CompatDB::Submit() {
54 back(); 54 back();
55 LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); 55 LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
56 Core::System::GetInstance().TelemetrySession().AddField( 56 Core::System::GetInstance().TelemetrySession().AddField(
57 Telemetry::FieldType::UserFeedback, "Compatibility", compatibility->checkedId()); 57 Common::Telemetry::FieldType::UserFeedback, "Compatibility",
58 compatibility->checkedId());
58 59
59 button(NextButton)->setEnabled(false); 60 button(NextButton)->setEnabled(false);
60 button(NextButton)->setText(tr("Submitting")); 61 button(NextButton)->setText(tr("Submitting"));
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index cb71b8d11..7af974d8d 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -13,10 +13,12 @@
13#include "input_common/udp/client.h" 13#include "input_common/udp/client.h"
14#include "yuzu/configuration/config.h" 14#include "yuzu/configuration/config.h"
15 15
16namespace FS = Common::FS;
17
16Config::Config(const std::string& config_file, bool is_global) { 18Config::Config(const std::string& config_file, bool is_global) {
17 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 19 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
18 qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file; 20 qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
19 FileUtil::CreateFullPath(qt_config_loc); 21 FS::CreateFullPath(qt_config_loc);
20 qt_config = 22 qt_config =
21 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); 23 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
22 global = is_global; 24 global = is_global;
@@ -464,41 +466,36 @@ void Config::ReadDataStorageValues() {
464 qt_config->beginGroup(QStringLiteral("Data Storage")); 466 qt_config->beginGroup(QStringLiteral("Data Storage"));
465 467
466 Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool(); 468 Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
467 FileUtil::GetUserPath( 469 FS::GetUserPath(FS::UserPath::NANDDir,
468 FileUtil::UserPath::NANDDir, 470 qt_config
469 qt_config 471 ->value(QStringLiteral("nand_directory"),
470 ->value(QStringLiteral("nand_directory"), 472 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)))
471 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))) 473 .toString()
472 .toString() 474 .toStdString());
473 .toStdString()); 475 FS::GetUserPath(FS::UserPath::SDMCDir,
474 FileUtil::GetUserPath( 476 qt_config
475 FileUtil::UserPath::SDMCDir, 477 ->value(QStringLiteral("sdmc_directory"),
476 qt_config 478 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)))
477 ->value(QStringLiteral("sdmc_directory"), 479 .toString()
478 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) 480 .toStdString());
479 .toString() 481 FS::GetUserPath(FS::UserPath::LoadDir,
480 .toStdString()); 482 qt_config
481 FileUtil::GetUserPath( 483 ->value(QStringLiteral("load_directory"),
482 FileUtil::UserPath::LoadDir, 484 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)))
483 qt_config 485 .toString()
484 ->value(QStringLiteral("load_directory"), 486 .toStdString());
485 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))) 487 FS::GetUserPath(FS::UserPath::DumpDir,
486 .toString() 488 qt_config
487 .toStdString()); 489 ->value(QStringLiteral("dump_directory"),
488 FileUtil::GetUserPath( 490 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)))
489 FileUtil::UserPath::DumpDir, 491 .toString()
490 qt_config 492 .toStdString());
491 ->value(QStringLiteral("dump_directory"), 493 FS::GetUserPath(FS::UserPath::CacheDir,
492 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))) 494 qt_config
493 .toString() 495 ->value(QStringLiteral("cache_directory"),
494 .toStdString()); 496 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)))
495 FileUtil::GetUserPath( 497 .toString()
496 FileUtil::UserPath::CacheDir, 498 .toStdString());
497 qt_config
498 ->value(QStringLiteral("cache_directory"),
499 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
500 .toString()
501 .toStdString());
502 Settings::values.gamecard_inserted = 499 Settings::values.gamecard_inserted =
503 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); 500 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
504 Settings::values.gamecard_current_game = 501 Settings::values.gamecard_current_game =
@@ -638,6 +635,11 @@ void Config::ReadCpuValues() {
638 ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); 635 ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool();
639 Settings::values.cpuopt_reduce_misalign_checks = 636 Settings::values.cpuopt_reduce_misalign_checks =
640 ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); 637 ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool();
638
639 Settings::values.cpuopt_unsafe_unfuse_fma =
640 ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool();
641 Settings::values.cpuopt_unsafe_reduce_fp_error =
642 ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool();
641 } 643 }
642 644
643 qt_config->endGroup(); 645 qt_config->endGroup();
@@ -677,11 +679,11 @@ void Config::ReadScreenshotValues() {
677 679
678 UISettings::values.enable_screenshot_save_as = 680 UISettings::values.enable_screenshot_save_as =
679 ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool(); 681 ReadSetting(QStringLiteral("enable_screenshot_save_as"), true).toBool();
680 FileUtil::GetUserPath( 682 FS::GetUserPath(
681 FileUtil::UserPath::ScreenshotsDir, 683 FS::UserPath::ScreenshotsDir,
682 qt_config 684 qt_config
683 ->value(QStringLiteral("screenshot_path"), QString::fromStdString(FileUtil::GetUserPath( 685 ->value(QStringLiteral("screenshot_path"),
684 FileUtil::UserPath::ScreenshotsDir))) 686 QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)))
685 .toString() 687 .toString()
686 .toStdString()); 688 .toStdString());
687 689
@@ -1016,20 +1018,20 @@ void Config::SaveDataStorageValues() {
1016 1018
1017 WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); 1019 WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
1018 WriteSetting(QStringLiteral("nand_directory"), 1020 WriteSetting(QStringLiteral("nand_directory"),
1019 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)), 1021 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)),
1020 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 1022 QString::fromStdString(FS::GetUserPath(FS::UserPath::NANDDir)));
1021 WriteSetting(QStringLiteral("sdmc_directory"), 1023 WriteSetting(QStringLiteral("sdmc_directory"),
1022 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), 1024 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)),
1023 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 1025 QString::fromStdString(FS::GetUserPath(FS::UserPath::SDMCDir)));
1024 WriteSetting(QStringLiteral("load_directory"), 1026 WriteSetting(QStringLiteral("load_directory"),
1025 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)), 1027 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)),
1026 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); 1028 QString::fromStdString(FS::GetUserPath(FS::UserPath::LoadDir)));
1027 WriteSetting(QStringLiteral("dump_directory"), 1029 WriteSetting(QStringLiteral("dump_directory"),
1028 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)), 1030 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)),
1029 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); 1031 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)));
1030 WriteSetting(QStringLiteral("cache_directory"), 1032 WriteSetting(QStringLiteral("cache_directory"),
1031 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)), 1033 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)),
1032 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); 1034 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)));
1033 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); 1035 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
1034 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, 1036 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
1035 false); 1037 false);
@@ -1135,6 +1137,11 @@ void Config::SaveCpuValues() {
1135 WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); 1137 WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true);
1136 WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), 1138 WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"),
1137 Settings::values.cpuopt_reduce_misalign_checks, true); 1139 Settings::values.cpuopt_reduce_misalign_checks, true);
1140
1141 WriteSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"),
1142 Settings::values.cpuopt_unsafe_unfuse_fma, true);
1143 WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
1144 Settings::values.cpuopt_unsafe_reduce_fp_error, true);
1138 } 1145 }
1139 1146
1140 qt_config->endGroup(); 1147 qt_config->endGroup();
@@ -1180,7 +1187,7 @@ void Config::SaveScreenshotValues() {
1180 WriteSetting(QStringLiteral("enable_screenshot_save_as"), 1187 WriteSetting(QStringLiteral("enable_screenshot_save_as"),
1181 UISettings::values.enable_screenshot_save_as); 1188 UISettings::values.enable_screenshot_save_as);
1182 WriteSetting(QStringLiteral("screenshot_path"), 1189 WriteSetting(QStringLiteral("screenshot_path"),
1183 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ScreenshotsDir))); 1190 QString::fromStdString(FS::GetUserPath(FS::UserPath::ScreenshotsDir)));
1184 1191
1185 qt_config->endGroup(); 1192 qt_config->endGroup();
1186} 1193}
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
index f9becab6e..18482795c 100644
--- a/src/yuzu/configuration/configuration_shared.cpp
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -72,18 +72,18 @@ void ConfigurationShared::SetPerGameSetting(
72 ConfigurationShared::USE_GLOBAL_OFFSET); 72 ConfigurationShared::USE_GLOBAL_OFFSET);
73} 73}
74 74
75void ConfigurationShared::SetHighlight(QWidget* widget, const std::string& name, bool highlighted) { 75void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
76 if (highlighted) { 76 if (highlighted) {
77 widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }") 77 widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
78 .arg(QString::fromStdString(name))); 78 .arg(widget->objectName()));
79 } else { 79 } else {
80 widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }") 80 widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
81 .arg(QString::fromStdString(name))); 81 .arg(widget->objectName()));
82 } 82 }
83 widget->show(); 83 widget->show();
84} 84}
85 85
86void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::string& name, 86void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
87 const Settings::Setting<bool>& setting, 87 const Settings::Setting<bool>& setting,
88 CheckState& tracker) { 88 CheckState& tracker) {
89 if (setting.UsingGlobal()) { 89 if (setting.UsingGlobal()) {
@@ -91,45 +91,39 @@ void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::str
91 } else { 91 } else {
92 tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off; 92 tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
93 } 93 }
94 SetHighlight(checkbox, name, tracker != CheckState::Global); 94 SetHighlight(checkbox, tracker != CheckState::Global);
95 QObject::connect(checkbox, &QCheckBox::clicked, checkbox, 95 QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
96 [checkbox, name, setting, &tracker]() { 96 tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
97 tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % 97 static_cast<int>(CheckState::Count));
98 static_cast<int>(CheckState::Count)); 98 if (tracker == CheckState::Global) {
99 if (tracker == CheckState::Global) { 99 checkbox->setChecked(setting.GetValue(true));
100 checkbox->setChecked(setting.GetValue(true)); 100 }
101 } 101 SetHighlight(checkbox, tracker != CheckState::Global);
102 SetHighlight(checkbox, name, tracker != CheckState::Global); 102 });
103 });
104} 103}
105 104
106void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, const std::string& name, 105void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
107 bool global, bool state, bool global_state, 106 bool global_state, CheckState& tracker) {
108 CheckState& tracker) {
109 if (global) { 107 if (global) {
110 tracker = CheckState::Global; 108 tracker = CheckState::Global;
111 } else { 109 } else {
112 tracker = (state == global_state) ? CheckState::On : CheckState::Off; 110 tracker = (state == global_state) ? CheckState::On : CheckState::Off;
113 } 111 }
114 SetHighlight(checkbox, name, tracker != CheckState::Global); 112 SetHighlight(checkbox, tracker != CheckState::Global);
115 QObject::connect(checkbox, &QCheckBox::clicked, checkbox, 113 QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
116 [checkbox, name, global_state, &tracker]() { 114 tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
117 tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) % 115 static_cast<int>(CheckState::Count));
118 static_cast<int>(CheckState::Count)); 116 if (tracker == CheckState::Global) {
119 if (tracker == CheckState::Global) { 117 checkbox->setChecked(global_state);
120 checkbox->setChecked(global_state); 118 }
121 } 119 SetHighlight(checkbox, tracker != CheckState::Global);
122 SetHighlight(checkbox, name, tracker != CheckState::Global); 120 });
123 });
124} 121}
125 122
126void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, 123void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
127 const std::string& target_name, int global) {
128 InsertGlobalItem(combobox, global); 124 InsertGlobalItem(combobox, global);
129 QObject::connect(combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), target, 125 QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
130 [target, target_name](int index) { 126 [target](int index) { SetHighlight(target, index != 0); });
131 ConfigurationShared::SetHighlight(target, target_name, index != 0);
132 });
133} 127}
134 128
135void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) { 129void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
index 003148c68..312b9e549 100644
--- a/src/yuzu/configuration/configuration_shared.h
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -39,13 +39,12 @@ void SetPerGameSetting(QComboBox* combobox,
39void SetPerGameSetting(QComboBox* combobox, 39void SetPerGameSetting(QComboBox* combobox,
40 const Settings::Setting<Settings::GPUAccuracy>* setting); 40 const Settings::Setting<Settings::GPUAccuracy>* setting);
41 41
42void SetHighlight(QWidget* widget, const std::string& name, bool highlighted); 42void SetHighlight(QWidget* widget, bool highlighted);
43void SetColoredTristate(QCheckBox* checkbox, const std::string& name, 43void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting,
44 const Settings::Setting<bool>& setting, CheckState& tracker); 44 CheckState& tracker);
45void SetColoredTristate(QCheckBox* checkbox, const std::string& name, bool global, bool state, 45void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
46 bool global_state, CheckState& tracker); 46 CheckState& tracker);
47void SetColoredComboBox(QComboBox* combobox, QWidget* target, const std::string& target_name, 47void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
48 int global);
49 48
50void InsertGlobalItem(QComboBox* combobox, int global_index); 49void InsertGlobalItem(QComboBox* combobox, int global_index);
51 50
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fea632531..fa9124ecf 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -59,7 +59,7 @@ void ConfigureAudio::SetConfiguration() {
59 ui->volume_combo_box->setCurrentIndex(1); 59 ui->volume_combo_box->setCurrentIndex(1);
60 ui->volume_slider->setEnabled(true); 60 ui->volume_slider->setEnabled(true);
61 } 61 }
62 ConfigurationShared::SetHighlight(ui->volume_layout, "volume_layout", 62 ConfigurationShared::SetHighlight(ui->volume_layout,
63 !Settings::values.volume.UsingGlobal()); 63 !Settings::values.volume.UsingGlobal());
64 } 64 }
65 SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); 65 SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
@@ -173,14 +173,13 @@ void ConfigureAudio::SetupPerGameUI() {
173 return; 173 return;
174 } 174 }
175 175
176 ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching, "toggle_audio_stretching", 176 ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
177 Settings::values.enable_audio_stretching, 177 Settings::values.enable_audio_stretching,
178 enable_audio_stretching); 178 enable_audio_stretching);
179 connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), 179 connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
180 this, [this](int index) { 180 ui->volume_slider->setEnabled(index == 1);
181 ui->volume_slider->setEnabled(index == 1); 181 ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
182 ConfigurationShared::SetHighlight(ui->volume_layout, "volume_layout", index == 1); 182 });
183 });
184 183
185 ui->output_sink_combo_box->setVisible(false); 184 ui->output_sink_combo_box->setVisible(false);
186 ui->output_sink_label->setVisible(false); 185 ui->output_sink_label->setVisible(false);
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 7493e5ffb..37fcd6adc 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -19,6 +19,8 @@ ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::Config
19 19
20 connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this, 20 connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this,
21 &ConfigureCpu::AccuracyUpdated); 21 &ConfigureCpu::AccuracyUpdated);
22 connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
23 &ConfigureCpu::UpdateGroup);
22} 24}
23 25
24ConfigureCpu::~ConfigureCpu() = default; 26ConfigureCpu::~ConfigureCpu() = default;
@@ -28,6 +30,12 @@ void ConfigureCpu::SetConfiguration() {
28 30
29 ui->accuracy->setEnabled(runtime_lock); 31 ui->accuracy->setEnabled(runtime_lock);
30 ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy)); 32 ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy));
33 UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy));
34
35 ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
36 ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma);
37 ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
38 ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error);
31} 39}
32 40
33void ConfigureCpu::AccuracyUpdated(int index) { 41void ConfigureCpu::AccuracyUpdated(int index) {
@@ -38,14 +46,21 @@ void ConfigureCpu::AccuracyUpdated(int index) {
38 QMessageBox::Yes | QMessageBox::No); 46 QMessageBox::Yes | QMessageBox::No);
39 if (result == QMessageBox::No) { 47 if (result == QMessageBox::No) {
40 ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate)); 48 ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate));
41 return; 49 UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate));
42 } 50 }
43 } 51 }
44} 52}
45 53
54void ConfigureCpu::UpdateGroup(int index) {
55 ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) ==
56 Settings::CPUAccuracy::Unsafe);
57}
58
46void ConfigureCpu::ApplyConfiguration() { 59void ConfigureCpu::ApplyConfiguration() {
47 Settings::values.cpu_accuracy = 60 Settings::values.cpu_accuracy =
48 static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); 61 static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex());
62 Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked();
63 Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked();
49} 64}
50 65
51void ConfigureCpu::changeEvent(QEvent* event) { 66void ConfigureCpu::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index e4741d3a4..3c5683d81 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -26,6 +26,7 @@ private:
26 void RetranslateUI(); 26 void RetranslateUI();
27 27
28 void AccuracyUpdated(int index); 28 void AccuracyUpdated(int index);
29 void UpdateGroup(int index);
29 30
30 void SetConfiguration(); 31 void SetConfiguration();
31 32
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index bf6ea79bb..ebdd2e6e9 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -40,6 +40,11 @@
40 </item> 40 </item>
41 <item> 41 <item>
42 <property name="text"> 42 <property name="text">
43 <string>Unsafe</string>
44 </property>
45 </item>
46 <item>
47 <property name="text">
43 <string>Enable Debug Mode</string> 48 <string>Enable Debug Mode</string>
44 </property> 49 </property>
45 </item> 50 </item>
@@ -63,6 +68,53 @@
63 </layout> 68 </layout>
64 </item> 69 </item>
65 <item> 70 <item>
71 <layout class="QVBoxLayout">
72 <item>
73 <widget class="QGroupBox" name="unsafe_group">
74 <property name="title">
75 <string>Unsafe CPU Optimization Settings</string>
76 </property>
77 <layout class="QVBoxLayout">
78 <item>
79 <widget class="QLabel">
80 <property name="wordWrap">
81 <bool>1</bool>
82 </property>
83 <property name="text">
84 <string>These settings reduce accuracy for speed.</string>
85 </property>
86 </widget>
87 </item>
88 <item>
89 <widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
90 <property name="text">
91 <string>Unfuse FMA (improve performance on CPUs without FMA)</string>
92 </property>
93 <property name="toolTip">
94 <string>
95 &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
96 </string>
97 </property>
98 </widget>
99 </item>
100 <item>
101 <widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
102 <property name="text">
103 <string>Faster FRSQRTE and FRECPE</string>
104 </property>
105 <property name="toolTip">
106 <string>
107 &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
108 </string>
109 </property>
110 </widget>
111 </item>
112 </layout>
113 </widget>
114 </item>
115 </layout>
116 </item>
117 <item>
66 <spacer name="verticalSpacer"> 118 <spacer name="verticalSpacer">
67 <property name="orientation"> 119 <property name="orientation">
68 <enum>Qt::Vertical</enum> 120 <enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index d0e71dd60..2bfe2c306 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -19,7 +19,8 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
19 SetConfiguration(); 19 SetConfiguration();
20 20
21 connect(ui->open_log_button, &QPushButton::clicked, []() { 21 connect(ui->open_log_button, &QPushButton::clicked, []() {
22 QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); 22 const auto path =
23 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
23 QDesktopServices::openUrl(QUrl::fromLocalFile(path)); 24 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
24 }); 25 });
25} 26}
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index a089f5733..7ab4a80f7 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -42,16 +42,16 @@ ConfigureFilesystem::~ConfigureFilesystem() = default;
42 42
43void ConfigureFilesystem::setConfiguration() { 43void ConfigureFilesystem::setConfiguration() {
44 ui->nand_directory_edit->setText( 44 ui->nand_directory_edit->setText(
45 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 45 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
46 ui->sdmc_directory_edit->setText( 46 ui->sdmc_directory_edit->setText(
47 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 47 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
48 ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); 48 ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
49 ui->dump_path_edit->setText( 49 ui->dump_path_edit->setText(
50 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); 50 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));
51 ui->load_path_edit->setText( 51 ui->load_path_edit->setText(
52 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); 52 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));
53 ui->cache_directory_edit->setText( 53 ui->cache_directory_edit->setText(
54 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); 54 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));
55 55
56 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); 56 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
57 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); 57 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
@@ -64,14 +64,16 @@ void ConfigureFilesystem::setConfiguration() {
64} 64}
65 65
66void ConfigureFilesystem::applyConfiguration() { 66void ConfigureFilesystem::applyConfiguration() {
67 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, 67 Common::FS::GetUserPath(Common::FS::UserPath::NANDDir,
68 ui->nand_directory_edit->text().toStdString()); 68 ui->nand_directory_edit->text().toStdString());
69 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, 69 Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir,
70 ui->sdmc_directory_edit->text().toStdString()); 70 ui->sdmc_directory_edit->text().toStdString());
71 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString()); 71 Common::FS::GetUserPath(Common::FS::UserPath::DumpDir,
72 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString()); 72 ui->dump_path_edit->text().toStdString());
73 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, 73 Common::FS::GetUserPath(Common::FS::UserPath::LoadDir,
74 ui->cache_directory_edit->text().toStdString()); 74 ui->load_path_edit->text().toStdString());
75 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir,
76 ui->cache_directory_edit->text().toStdString());
75 Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString(); 77 Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
76 78
77 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); 79 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
@@ -121,12 +123,13 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
121} 123}
122 124
123void ConfigureFilesystem::ResetMetadata() { 125void ConfigureFilesystem::ResetMetadata() {
124 if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + 126 if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
125 "game_list")) { 127 "game_list")) {
126 QMessageBox::information(this, tr("Reset Metadata Cache"), 128 QMessageBox::information(this, tr("Reset Metadata Cache"),
127 tr("The metadata cache is already empty.")); 129 tr("The metadata cache is already empty."));
128 } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + 130 } else if (Common::FS::DeleteDirRecursively(
129 DIR_SEP + "game_list")) { 131 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
132 "game_list")) {
130 QMessageBox::information(this, tr("Reset Metadata Cache"), 133 QMessageBox::information(this, tr("Reset Metadata Cache"),
131 tr("The operation completed successfully.")); 134 tr("The operation completed successfully."));
132 UISettings::values.is_game_list_reload_pending.exchange(true); 135 UISettings::values.is_game_list_reload_pending.exchange(true);
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index c0dbd9855..830096ea0 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -105,10 +105,10 @@ void ConfigureGeneral::SetupPerGameUI() {
105 ui->toggle_background_pause->setVisible(false); 105 ui->toggle_background_pause->setVisible(false);
106 ui->toggle_hide_mouse->setVisible(false); 106 ui->toggle_hide_mouse->setVisible(false);
107 107
108 ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, "toggle_frame_limit", 108 ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit,
109 Settings::values.use_frame_limit, use_frame_limit); 109 Settings::values.use_frame_limit, use_frame_limit);
110 ConfigurationShared::SetColoredTristate(ui->use_multi_core, "use_multi_core", 110 ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
111 Settings::values.use_multi_core, use_multi_core); 111 use_multi_core);
112 112
113 connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() { 113 connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() {
114 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() && 114 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() &&
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 3e42531c3..07d818548 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -34,9 +34,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
34 connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { 34 connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
35 UpdateDeviceComboBox(); 35 UpdateDeviceComboBox();
36 if (!Settings::configuring_global) { 36 if (!Settings::configuring_global) {
37 ConfigurationShared::SetHighlight(ui->api_layout, "api_layout", 37 ConfigurationShared::SetHighlight(
38 ui->api->currentIndex() != 38 ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
39 ConfigurationShared::USE_GLOBAL_INDEX);
40 } 39 }
41 }); 40 });
42 connect(ui->device, qOverload<int>(&QComboBox::activated), this, 41 connect(ui->device, qOverload<int>(&QComboBox::activated), this,
@@ -80,17 +79,16 @@ void ConfigureGraphics::SetConfiguration() {
80 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); 79 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
81 } else { 80 } else {
82 ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); 81 ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
83 ConfigurationShared::SetHighlight(ui->api_layout, "api_layout", 82 ConfigurationShared::SetHighlight(ui->api_layout,
84 !Settings::values.renderer_backend.UsingGlobal()); 83 !Settings::values.renderer_backend.UsingGlobal());
85 ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, 84 ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
86 &Settings::values.aspect_ratio); 85 &Settings::values.aspect_ratio);
87 86
88 ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); 87 ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
89 ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); 88 ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
90 ConfigurationShared::SetHighlight(ui->ar_label, "ar_label", 89 ConfigurationShared::SetHighlight(ui->ar_label,
91 !Settings::values.aspect_ratio.UsingGlobal()); 90 !Settings::values.aspect_ratio.UsingGlobal());
92 ConfigurationShared::SetHighlight(ui->bg_layout, "bg_layout", 91 ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
93 !Settings::values.bg_red.UsingGlobal());
94 } 92 }
95 93
96 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(), 94 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(),
@@ -248,20 +246,18 @@ void ConfigureGraphics::SetupPerGameUI() {
248 return; 246 return;
249 } 247 }
250 248
251 connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, 249 connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
252 [this](int index) { 250 ui->bg_button->setEnabled(index == 1);
253 ui->bg_button->setEnabled(index == 1); 251 ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
254 ConfigurationShared::SetHighlight(ui->bg_layout, "bg_layout", index == 1); 252 });
255 });
256 253
257 ConfigurationShared::SetColoredTristate(ui->use_disk_shader_cache, "use_disk_shader_cache",
258 Settings::values.use_disk_shader_cache,
259 use_disk_shader_cache);
260 ConfigurationShared::SetColoredTristate( 254 ConfigurationShared::SetColoredTristate(
261 ui->use_asynchronous_gpu_emulation, "use_asynchronous_gpu_emulation", 255 ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
262 Settings::values.use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); 256 ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
257 Settings::values.use_asynchronous_gpu_emulation,
258 use_asynchronous_gpu_emulation);
263 259
264 ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label, "ar_label", 260 ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
265 Settings::values.aspect_ratio.GetValue(true)); 261 Settings::values.aspect_ratio.GetValue(true));
266 ConfigurationShared::InsertGlobalItem( 262 ConfigurationShared::InsertGlobalItem(
267 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); 263 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index c5d1a778c..73f276949 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -41,9 +41,9 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
41 ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); 41 ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
42 ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, 42 ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
43 &Settings::values.max_anisotropy); 43 &Settings::values.max_anisotropy);
44 ConfigurationShared::SetHighlight(ui->label_gpu_accuracy, "label_gpu_accuracy", 44 ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
45 !Settings::values.gpu_accuracy.UsingGlobal()); 45 !Settings::values.gpu_accuracy.UsingGlobal());
46 ConfigurationShared::SetHighlight(ui->af_label, "af_label", 46 ConfigurationShared::SetHighlight(ui->af_label,
47 !Settings::values.max_anisotropy.UsingGlobal()); 47 !Settings::values.max_anisotropy.UsingGlobal());
48 } 48 }
49} 49}
@@ -131,20 +131,18 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
131 return; 131 return;
132 } 132 }
133 133
134 ConfigurationShared::SetColoredTristate(ui->use_vsync, "use_vsync", Settings::values.use_vsync, 134 ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
135 use_vsync);
136 ConfigurationShared::SetColoredTristate(ui->use_assembly_shaders, "use_assembly_shaders",
137 Settings::values.use_assembly_shaders,
138 use_assembly_shaders);
139 ConfigurationShared::SetColoredTristate( 135 ConfigurationShared::SetColoredTristate(
140 ui->use_asynchronous_shaders, "use_asynchronous_shaders", 136 ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);
141 Settings::values.use_asynchronous_shaders, use_asynchronous_shaders); 137 ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
142 ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, "use_fast_gpu_time", 138 Settings::values.use_asynchronous_shaders,
139 use_asynchronous_shaders);
140 ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
143 Settings::values.use_fast_gpu_time, use_fast_gpu_time); 141 Settings::values.use_fast_gpu_time, use_fast_gpu_time);
144 ConfigurationShared::SetColoredComboBox( 142 ConfigurationShared::SetColoredComboBox(
145 ui->gpu_accuracy, ui->label_gpu_accuracy, "label_gpu_accuracy", 143 ui->gpu_accuracy, ui->label_gpu_accuracy,
146 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); 144 static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
147 ConfigurationShared::SetColoredComboBox( 145 ConfigurationShared::SetColoredComboBox(
148 ui->anisotropic_filtering_combobox, ui->af_label, "af_label", 146 ui->anisotropic_filtering_combobox, ui->af_label,
149 static_cast<int>(Settings::values.max_anisotropy.GetValue(true))); 147 static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
150} 148}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index a793c803d..846a30586 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -92,7 +92,7 @@
92 <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> 92 <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
93 </property> 93 </property>
94 <property name="text"> 94 <property name="text">
95 <string>Use asynchronous shader building (experimental, OpenGL or Assembly shaders only)</string> 95 <string>Use asynchronous shader building (experimental)</string>
96 </property> 96 </property>
97 </widget> 97 </widget>
98 </item> 98 </item>
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 6f7fd4414..cbee51a5e 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -154,7 +154,7 @@ void ConfigureHotkeys::ClearAll() {
154 const QStandardItem* parent = model->item(r, 0); 154 const QStandardItem* parent = model->item(r, 0);
155 155
156 for (int r2 = 0; r2 < parent->rowCount(); ++r2) { 156 for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
157 model->item(r, 0)->child(r2, 1)->setText(tr("")); 157 model->item(r, 0)->child(r2, 1)->setText(QString{});
158 } 158 }
159 } 159 }
160} 160}
@@ -186,7 +186,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
186 model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); 186 model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
187 } 187 }
188 }); 188 });
189 connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, tr("")); }); 189 connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
190 190
191 context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); 191 context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
192} 192}
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 478d5d3a1..793fd8975 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -79,8 +79,8 @@ void ConfigurePerGameAddons::ApplyConfiguration() {
79 std::sort(disabled_addons.begin(), disabled_addons.end()); 79 std::sort(disabled_addons.begin(), disabled_addons.end());
80 std::sort(current.begin(), current.end()); 80 std::sort(current.begin(), current.end());
81 if (disabled_addons != current) { 81 if (disabled_addons != current) {
82 FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + 82 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
83 "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); 83 "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
84 } 84 }
85 85
86 Settings::values.disabled_addons[title_id] = disabled_addons; 86 Settings::values.disabled_addons[title_id] = disabled_addons;
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index f53423440..6334c4c50 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -34,7 +34,7 @@ constexpr std::array<u8, 107> backup_jpeg{
34}; 34};
35 35
36QString GetImagePath(Common::UUID uuid) { 36QString GetImagePath(Common::UUID uuid) {
37 const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 37 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
38 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 38 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
39 return QString::fromStdString(path); 39 return QString::fromStdString(path);
40} 40}
@@ -282,7 +282,7 @@ void ConfigureProfileManager::SetUserImage() {
282 } 282 }
283 283
284 const auto raw_path = QString::fromStdString( 284 const auto raw_path = QString::fromStdString(
285 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); 285 Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010");
286 const QFileInfo raw_info{raw_path}; 286 const QFileInfo raw_info{raw_path};
287 if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { 287 if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
288 QMessageBox::warning(this, tr("Error deleting file"), 288 QMessageBox::warning(this, tr("Error deleting file"),
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 0c4daf147..9ad43ed8f 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -90,13 +90,13 @@ void ConfigureSystem::SetConfiguration() {
90 &Settings::values.time_zone_index); 90 &Settings::values.time_zone_index);
91 ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); 91 ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
92 92
93 ConfigurationShared::SetHighlight(ui->label_language, "label_language", 93 ConfigurationShared::SetHighlight(ui->label_language,
94 !Settings::values.language_index.UsingGlobal()); 94 !Settings::values.language_index.UsingGlobal());
95 ConfigurationShared::SetHighlight(ui->label_region, "label_region", 95 ConfigurationShared::SetHighlight(ui->label_region,
96 !Settings::values.region_index.UsingGlobal()); 96 !Settings::values.region_index.UsingGlobal());
97 ConfigurationShared::SetHighlight(ui->label_timezone, "label_timezone", 97 ConfigurationShared::SetHighlight(ui->label_timezone,
98 !Settings::values.time_zone_index.UsingGlobal()); 98 !Settings::values.time_zone_index.UsingGlobal());
99 ConfigurationShared::SetHighlight(ui->label_sound, "label_sound", 99 ConfigurationShared::SetHighlight(ui->label_sound,
100 !Settings::values.sound_index.UsingGlobal()); 100 !Settings::values.sound_index.UsingGlobal());
101 } 101 }
102} 102}
@@ -224,22 +224,20 @@ void ConfigureSystem::SetupPerGameUI() {
224 } 224 }
225 225
226 ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language, 226 ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
227 "label_language",
228 Settings::values.language_index.GetValue(true)); 227 Settings::values.language_index.GetValue(true));
229 ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region, "label_region", 228 ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
230 Settings::values.region_index.GetValue(true)); 229 Settings::values.region_index.GetValue(true));
231 ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone, 230 ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
232 "label_timezone",
233 Settings::values.time_zone_index.GetValue(true)); 231 Settings::values.time_zone_index.GetValue(true));
234 ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound, "label_sound", 232 ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound,
235 Settings::values.sound_index.GetValue(true)); 233 Settings::values.sound_index.GetValue(true));
236 234
237 ConfigurationShared::SetColoredTristate( 235 ConfigurationShared::SetColoredTristate(
238 ui->rng_seed_checkbox, "rng_seed_checkbox", Settings::values.rng_seed.UsingGlobal(), 236 ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
239 Settings::values.rng_seed.GetValue().has_value(), 237 Settings::values.rng_seed.GetValue().has_value(),
240 Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); 238 Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
241 ConfigurationShared::SetColoredTristate( 239 ConfigurationShared::SetColoredTristate(
242 ui->custom_rtc_checkbox, "custom_rtc_checkbox", Settings::values.custom_rtc.UsingGlobal(), 240 ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(),
243 Settings::values.custom_rtc.GetValue().has_value(), 241 Settings::values.custom_rtc.GetValue().has_value(),
244 Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc); 242 Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc);
245} 243}
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 2c20b68d0..dbe3f78c8 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -61,9 +61,9 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur
61 // Set screenshot path to user specification. 61 // Set screenshot path to user specification.
62 connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] { 62 connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] {
63 const QString& filename = 63 const QString& filename =
64 QFileDialog::getExistingDirectory( 64 QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."),
65 this, tr("Select Screenshots Path..."), 65 QString::fromStdString(Common::FS::GetUserPath(
66 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ScreenshotsDir))) + 66 Common::FS::UserPath::ScreenshotsDir))) +
67 QDir::separator(); 67 QDir::separator();
68 if (!filename.isEmpty()) { 68 if (!filename.isEmpty()) {
69 ui->screenshot_path_edit->setText(filename); 69 ui->screenshot_path_edit->setText(filename);
@@ -82,8 +82,8 @@ void ConfigureUi::ApplyConfiguration() {
82 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); 82 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
83 83
84 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); 84 UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
85 FileUtil::GetUserPath(FileUtil::UserPath::ScreenshotsDir, 85 Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir,
86 ui->screenshot_path_edit->text().toStdString()); 86 ui->screenshot_path_edit->text().toStdString());
87 Settings::Apply(); 87 Settings::Apply();
88} 88}
89 89
@@ -101,7 +101,7 @@ void ConfigureUi::SetConfiguration() {
101 101
102 ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as); 102 ui->enable_screenshot_save_as->setChecked(UISettings::values.enable_screenshot_save_as);
103 ui->screenshot_path_edit->setText( 103 ui->screenshot_path_edit->setText(
104 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ScreenshotsDir))); 104 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir)));
105} 105}
106 106
107void ConfigureUi::changeEvent(QEvent* event) { 107void ConfigureUi::changeEvent(QEvent* event) {
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 53049ffd6..0e26f765b 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -109,8 +109,7 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
109 MicroProfileSetDisplayMode(1); // Timers screen 109 MicroProfileSetDisplayMode(1); // Timers screen
110 MicroProfileInitUI(); 110 MicroProfileInitUI();
111 111
112 connect(&update_timer, &QTimer::timeout, this, 112 connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update));
113 static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update));
114} 113}
115 114
116void MicroProfileWidget::paintEvent(QPaintEvent* ev) { 115void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 967ef4a21..6a71d9644 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -502,10 +502,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string pat
502 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); 502 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
503 503
504 connect(open_save_location, &QAction::triggered, [this, program_id, path]() { 504 connect(open_save_location, &QAction::triggered, [this, program_id, path]() {
505 emit OpenFolderRequested(GameListOpenTarget::SaveData, path); 505 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
506 }); 506 });
507 connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { 507 connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
508 emit OpenFolderRequested(GameListOpenTarget::ModData, path); 508 emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
509 }); 509 });
510 connect(open_transferable_shader_cache, &QAction::triggered, 510 connect(open_transferable_shader_cache, &QAction::triggered,
511 [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); 511 [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 483835cce..78e2ba169 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -84,7 +84,8 @@ public:
84signals: 84signals:
85 void GameChosen(QString game_path); 85 void GameChosen(QString game_path);
86 void ShouldCancelWorker(); 86 void ShouldCancelWorker();
87 void OpenFolderRequested(GameListOpenTarget target, const std::string& game_path); 87 void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
88 const std::string& game_path);
88 void OpenTransferableShaderCacheRequested(u64 program_id); 89 void OpenTransferableShaderCacheRequested(u64 program_id);
89 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 90 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
90 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target); 91 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index c9a395222..e0ce45fd9 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -39,12 +39,12 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
39 return generator(); 39 return generator();
40 } 40 }
41 41
42 const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + 42 const auto path = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
43 DIR_SEP + filename + '.' + ext; 43 "game_list" + DIR_SEP + filename + '.' + ext;
44 44
45 FileUtil::CreateFullPath(path); 45 Common::FS::CreateFullPath(path);
46 46
47 if (!FileUtil::Exists(path)) { 47 if (!Common::FS::Exists(path)) {
48 const auto str = generator(); 48 const auto str = generator();
49 49
50 QFile file{QString::fromStdString(path)}; 50 QFile file{QString::fromStdString(path)};
@@ -70,14 +70,14 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
70 return generator(); 70 return generator();
71 } 71 }
72 72
73 const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + 73 const auto path1 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
74 DIR_SEP + filename + ".jpeg"; 74 "game_list" + DIR_SEP + filename + ".jpeg";
75 const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + 75 const auto path2 = Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
76 DIR_SEP + filename + ".appname.txt"; 76 "game_list" + DIR_SEP + filename + ".appname.txt";
77 77
78 FileUtil::CreateFullPath(path1); 78 Common::FS::CreateFullPath(path1);
79 79
80 if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) { 80 if (!Common::FS::Exists(path1) || !Common::FS::Exists(path2)) {
81 const auto [icon, nacp] = generator(); 81 const auto [icon, nacp] = generator();
82 82
83 QFile file1{QString::fromStdString(path1)}; 83 QFile file1{QString::fromStdString(path1)};
@@ -208,7 +208,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
208 file_type_string, program_id), 208 file_type_string, program_id),
209 new GameListItemCompat(compatibility), 209 new GameListItemCompat(compatibility),
210 new GameListItem(file_type_string), 210 new GameListItem(file_type_string),
211 new GameListItemSize(FileUtil::GetSize(path)), 211 new GameListItemSize(Common::FS::GetSize(path)),
212 }; 212 };
213 213
214 if (UISettings::values.show_add_ons) { 214 if (UISettings::values.show_add_ons) {
@@ -289,7 +289,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
289 } 289 }
290 290
291 const std::string physical_name = directory + DIR_SEP + virtual_name; 291 const std::string physical_name = directory + DIR_SEP + virtual_name;
292 const bool is_dir = FileUtil::IsDirectory(physical_name); 292 const bool is_dir = Common::FS::IsDirectory(physical_name);
293 if (!is_dir && 293 if (!is_dir &&
294 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { 294 (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
295 const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); 295 const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
@@ -345,7 +345,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
345 return true; 345 return true;
346 }; 346 };
347 347
348 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); 348 Common::FS::ForeachDirectoryEntry(nullptr, dir_path, callback);
349} 349}
350 350
351void GameListWorker::run() { 351void GameListWorker::run() {
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 592993c36..cd7e78eb4 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -177,8 +177,8 @@ static void InitializeLogging() {
177 log_filter.ParseFilterString(Settings::values.log_filter); 177 log_filter.ParseFilterString(Settings::values.log_filter);
178 Log::SetGlobalFilter(log_filter); 178 Log::SetGlobalFilter(log_filter);
179 179
180 const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); 180 const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
181 FileUtil::CreateFullPath(log_dir); 181 Common::FS::CreateFullPath(log_dir);
182 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); 182 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
183#ifdef _WIN32 183#ifdef _WIN32
184 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); 184 Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -894,6 +894,8 @@ void GMainWindow::ConnectMenuEvents() {
894 connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); 894 connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ);
895 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); 895 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
896 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); 896 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
897 connect(ui.action_Configure_Current_Game, &QAction::triggered, this,
898 &GMainWindow::OnConfigurePerGame);
897 899
898 // View 900 // View
899 connect(ui.action_Single_Window_Mode, &QAction::triggered, this, 901 connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
@@ -1039,7 +1041,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
1039 } 1041 }
1040 game_path = filename; 1042 game_path = filename;
1041 1043
1042 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "Qt"); 1044 system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
1043 return true; 1045 return true;
1044} 1046}
1045 1047
@@ -1121,7 +1123,7 @@ void GMainWindow::BootGame(const QString& filename) {
1121 title_name = metadata.first->GetApplicationName(); 1123 title_name = metadata.first->GetApplicationName();
1122 } 1124 }
1123 if (res != Loader::ResultStatus::Success || title_name.empty()) { 1125 if (res != Loader::ResultStatus::Success || title_name.empty()) {
1124 title_name = FileUtil::GetFilename(filename.toStdString()); 1126 title_name = Common::FS::GetFilename(filename.toStdString());
1125 } 1127 }
1126 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); 1128 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
1127 UpdateWindowTitle(title_name, title_version); 1129 UpdateWindowTitle(title_name, title_version);
@@ -1167,6 +1169,7 @@ void GMainWindow::ShutdownGame() {
1167 ui.action_Pause->setEnabled(false); 1169 ui.action_Pause->setEnabled(false);
1168 ui.action_Stop->setEnabled(false); 1170 ui.action_Stop->setEnabled(false);
1169 ui.action_Restart->setEnabled(false); 1171 ui.action_Restart->setEnabled(false);
1172 ui.action_Configure_Current_Game->setEnabled(false);
1170 ui.action_Report_Compatibility->setEnabled(false); 1173 ui.action_Report_Compatibility->setEnabled(false);
1171 ui.action_Load_Amiibo->setEnabled(false); 1174 ui.action_Load_Amiibo->setEnabled(false);
1172 ui.action_Capture_Screenshot->setEnabled(false); 1175 ui.action_Capture_Screenshot->setEnabled(false);
@@ -1239,27 +1242,36 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
1239 BootGame(game_path); 1242 BootGame(game_path);
1240} 1243}
1241 1244
1242void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path) { 1245void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
1246 const std::string& game_path) {
1243 std::string path; 1247 std::string path;
1244 QString open_target; 1248 QString open_target;
1245 1249
1246 const auto v_file = Core::GetGameFileFromPath(vfs, game_path); 1250 const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] {
1247 const auto loader = Loader::GetLoader(v_file); 1251 FileSys::PatchManager pm{program_id};
1248 FileSys::NACP control{}; 1252 const auto control = pm.GetControlMetadata().first;
1249 u64 program_id{}; 1253 if (control != nullptr) {
1254 return std::make_pair(control->GetDefaultNormalSaveSize(),
1255 control->GetDeviceSaveDataSize());
1256 } else {
1257 const auto file = Core::GetGameFileFromPath(vfs, game_path);
1258 const auto loader = Loader::GetLoader(file);
1250 1259
1251 loader->ReadControlData(control); 1260 FileSys::NACP nacp{};
1252 loader->ReadProgramId(program_id); 1261 loader->ReadControlData(nacp);
1262 return std::make_pair(nacp.GetDefaultNormalSaveSize(), nacp.GetDeviceSaveDataSize());
1263 }
1264 }();
1253 1265
1254 const bool has_user_save{control.GetDefaultNormalSaveSize() > 0}; 1266 const bool has_user_save{user_save_size > 0};
1255 const bool has_device_save{control.GetDeviceSaveDataSize() > 0}; 1267 const bool has_device_save{device_save_size > 0};
1256 1268
1257 ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?"); 1269 ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?");
1258 1270
1259 switch (target) { 1271 switch (target) {
1260 case GameListOpenTarget::SaveData: { 1272 case GameListOpenTarget::SaveData: {
1261 open_target = tr("Save Data"); 1273 open_target = tr("Save Data");
1262 const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); 1274 const std::string nand_dir = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir);
1263 1275
1264 if (has_user_save) { 1276 if (has_user_save) {
1265 // User save data 1277 // User save data
@@ -1294,16 +1306,16 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str
1294 FileSys::SaveDataType::SaveData, program_id, {}, 0); 1306 FileSys::SaveDataType::SaveData, program_id, {}, 0);
1295 } 1307 }
1296 1308
1297 if (!FileUtil::Exists(path)) { 1309 if (!Common::FS::Exists(path)) {
1298 FileUtil::CreateFullPath(path); 1310 Common::FS::CreateFullPath(path);
1299 FileUtil::CreateDir(path); 1311 Common::FS::CreateDir(path);
1300 } 1312 }
1301 1313
1302 break; 1314 break;
1303 } 1315 }
1304 case GameListOpenTarget::ModData: { 1316 case GameListOpenTarget::ModData: {
1305 open_target = tr("Mod Data"); 1317 open_target = tr("Mod Data");
1306 const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); 1318 const auto load_dir = Common::FS::GetUserPath(Common::FS::UserPath::LoadDir);
1307 path = fmt::format("{}{:016X}", load_dir, program_id); 1319 path = fmt::format("{}{:016X}", load_dir, program_id);
1308 break; 1320 break;
1309 } 1321 }
@@ -1325,7 +1337,7 @@ void GMainWindow::OnGameListOpenFolder(GameListOpenTarget target, const std::str
1325 1337
1326void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { 1338void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) {
1327 const QString shader_dir = 1339 const QString shader_dir =
1328 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); 1340 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
1329 const QString transferable_shader_cache_folder_path = 1341 const QString transferable_shader_cache_folder_path =
1330 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); 1342 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
1331 const QString transferable_shader_cache_file_path = 1343 const QString transferable_shader_cache_file_path =
@@ -1428,8 +1440,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
1428 RemoveAddOnContent(program_id, entry_type); 1440 RemoveAddOnContent(program_id, entry_type);
1429 break; 1441 break;
1430 } 1442 }
1431 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + 1443 Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
1432 "game_list"); 1444 DIR_SEP + "game_list");
1433 game_list->PopulateAsync(UISettings::values.game_dirs); 1445 game_list->PopulateAsync(UISettings::values.game_dirs);
1434} 1446}
1435 1447
@@ -1519,7 +1531,7 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
1519 1531
1520void GMainWindow::RemoveTransferableShaderCache(u64 program_id) { 1532void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
1521 const QString shader_dir = 1533 const QString shader_dir =
1522 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); 1534 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir));
1523 const QString transferable_shader_cache_folder_path = 1535 const QString transferable_shader_cache_folder_path =
1524 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); 1536 shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable");
1525 const QString transferable_shader_cache_file_path = 1537 const QString transferable_shader_cache_file_path =
@@ -1543,7 +1555,7 @@ void GMainWindow::RemoveTransferableShaderCache(u64 program_id) {
1543 1555
1544void GMainWindow::RemoveCustomConfiguration(u64 program_id) { 1556void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
1545 const QString config_dir = 1557 const QString config_dir =
1546 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir)); 1558 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
1547 const QString custom_config_file_path = 1559 const QString custom_config_file_path =
1548 config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id)); 1560 config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id));
1549 1561
@@ -1590,7 +1602,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1590 } 1602 }
1591 1603
1592 const auto path = fmt::format( 1604 const auto path = fmt::format(
1593 "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id); 1605 "{}{:016X}/romfs", Common::FS::GetUserPath(Common::FS::UserPath::DumpDir), *romfs_title_id);
1594 1606
1595 FileSys::VirtualFile romfs; 1607 FileSys::VirtualFile romfs;
1596 1608
@@ -1670,13 +1682,13 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1670void GMainWindow::OnGameListOpenDirectory(const QString& directory) { 1682void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1671 QString path; 1683 QString path;
1672 if (directory == QStringLiteral("SDMC")) { 1684 if (directory == QStringLiteral("SDMC")) {
1673 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + 1685 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir) +
1674 "Nintendo/Contents/registered"); 1686 "Nintendo/Contents/registered");
1675 } else if (directory == QStringLiteral("UserNAND")) { 1687 } else if (directory == QStringLiteral("UserNAND")) {
1676 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 1688 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
1677 "user/Contents/registered"); 1689 "user/Contents/registered");
1678 } else if (directory == QStringLiteral("SysNAND")) { 1690 } else if (directory == QStringLiteral("SysNAND")) {
1679 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 1691 path = QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
1680 "system/Contents/registered"); 1692 "system/Contents/registered");
1681 } else { 1693 } else {
1682 path = directory; 1694 path = directory;
@@ -1690,8 +1702,10 @@ void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1690 1702
1691void GMainWindow::OnGameListAddDirectory() { 1703void GMainWindow::OnGameListAddDirectory() {
1692 const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); 1704 const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1693 if (dir_path.isEmpty()) 1705 if (dir_path.isEmpty()) {
1694 return; 1706 return;
1707 }
1708
1695 UISettings::GameDir game_dir{dir_path, false, true}; 1709 UISettings::GameDir game_dir{dir_path, false, true};
1696 if (!UISettings::values.game_dirs.contains(game_dir)) { 1710 if (!UISettings::values.game_dirs.contains(game_dir)) {
1697 UISettings::values.game_dirs.append(game_dir); 1711 UISettings::values.game_dirs.append(game_dir);
@@ -1718,26 +1732,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1718 return; 1732 return;
1719 } 1733 }
1720 1734
1721 ConfigurePerGame dialog(this, title_id); 1735 OpenPerGameConfiguration(title_id, file);
1722 dialog.LoadFromFile(v_file);
1723 auto result = dialog.exec();
1724 if (result == QDialog::Accepted) {
1725 dialog.ApplyConfiguration();
1726
1727 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1728 if (reload) {
1729 game_list->PopulateAsync(UISettings::values.game_dirs);
1730 }
1731
1732 // Do not cause the global config to write local settings into the config file
1733 Settings::RestoreGlobalState();
1734
1735 if (!Core::System::GetInstance().IsPoweredOn()) {
1736 config->Save();
1737 }
1738 } else {
1739 Settings::RestoreGlobalState();
1740 }
1741} 1736}
1742 1737
1743void GMainWindow::OnMenuLoadFile() { 1738void GMainWindow::OnMenuLoadFile() {
@@ -1882,8 +1877,8 @@ void GMainWindow::OnMenuInstallToNAND() {
1882 : tr("%n file(s) failed to install\n", "", failed_files.size())); 1877 : tr("%n file(s) failed to install\n", "", failed_files.size()));
1883 1878
1884 QMessageBox::information(this, tr("Install Results"), install_results); 1879 QMessageBox::information(this, tr("Install Results"), install_results);
1885 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + 1880 Common::FS::DeleteDirRecursively(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
1886 "game_list"); 1881 DIR_SEP + "game_list");
1887 game_list->PopulateAsync(UISettings::values.game_dirs); 1882 game_list->PopulateAsync(UISettings::values.game_dirs);
1888 ui.action_Install_File_NAND->setEnabled(true); 1883 ui.action_Install_File_NAND->setEnabled(true);
1889} 1884}
@@ -2066,6 +2061,7 @@ void GMainWindow::OnStartGame() {
2066 ui.action_Pause->setEnabled(true); 2061 ui.action_Pause->setEnabled(true);
2067 ui.action_Stop->setEnabled(true); 2062 ui.action_Stop->setEnabled(true);
2068 ui.action_Restart->setEnabled(true); 2063 ui.action_Restart->setEnabled(true);
2064 ui.action_Configure_Current_Game->setEnabled(true);
2069 ui.action_Report_Compatibility->setEnabled(true); 2065 ui.action_Report_Compatibility->setEnabled(true);
2070 2066
2071 discord_rpc->Update(); 2067 discord_rpc->Update();
@@ -2255,6 +2251,36 @@ void GMainWindow::OnConfigure() {
2255 UpdateStatusButtons(); 2251 UpdateStatusButtons();
2256} 2252}
2257 2253
2254void GMainWindow::OnConfigurePerGame() {
2255 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
2256 OpenPerGameConfiguration(title_id, game_path.toStdString());
2257}
2258
2259void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
2260 const auto v_file = Core::GetGameFileFromPath(vfs, file_name);
2261
2262 ConfigurePerGame dialog(this, title_id);
2263 dialog.LoadFromFile(v_file);
2264 auto result = dialog.exec();
2265 if (result == QDialog::Accepted) {
2266 dialog.ApplyConfiguration();
2267
2268 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
2269 if (reload) {
2270 game_list->PopulateAsync(UISettings::values.game_dirs);
2271 }
2272
2273 // Do not cause the global config to write local settings into the config file
2274 Settings::RestoreGlobalState();
2275
2276 if (!Core::System::GetInstance().IsPoweredOn()) {
2277 config->Save();
2278 }
2279 } else {
2280 Settings::RestoreGlobalState();
2281 }
2282}
2283
2258void GMainWindow::OnLoadAmiibo() { 2284void GMainWindow::OnLoadAmiibo() {
2259 const QString extensions{QStringLiteral("*.bin")}; 2285 const QString extensions{QStringLiteral("*.bin")};
2260 const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); 2286 const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
@@ -2302,7 +2328,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
2302 2328
2303void GMainWindow::OnOpenYuzuFolder() { 2329void GMainWindow::OnOpenYuzuFolder() {
2304 QDesktopServices::openUrl(QUrl::fromLocalFile( 2330 QDesktopServices::openUrl(QUrl::fromLocalFile(
2305 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir)))); 2331 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::UserDir))));
2306} 2332}
2307 2333
2308void GMainWindow::OnAbout() { 2334void GMainWindow::OnAbout() {
@@ -2324,7 +2350,7 @@ void GMainWindow::OnCaptureScreenshot() {
2324 2350
2325 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 2351 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
2326 const auto screenshot_path = 2352 const auto screenshot_path =
2327 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ScreenshotsDir)); 2353 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir));
2328 const auto date = 2354 const auto date =
2329 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz")); 2355 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh-mm-ss-zzz"));
2330 QString filename = QStringLiteral("%1%2_%3.png") 2356 QString filename = QStringLiteral("%1%2_%3.png")
@@ -2527,18 +2553,18 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
2527 if (res == QMessageBox::Cancel) 2553 if (res == QMessageBox::Cancel)
2528 return; 2554 return;
2529 2555
2530 FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + 2556 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
2531 "prod.keys_autogenerated"); 2557 "prod.keys_autogenerated");
2532 FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + 2558 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
2533 "console.keys_autogenerated"); 2559 "console.keys_autogenerated");
2534 FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + 2560 Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::KeysDir) +
2535 "title.keys_autogenerated"); 2561 "title.keys_autogenerated");
2536 } 2562 }
2537 2563
2538 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 2564 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
2539 if (keys.BaseDeriveNecessary()) { 2565 if (keys.BaseDeriveNecessary()) {
2540 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( 2566 Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
2541 FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; 2567 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), FileSys::Mode::Read)};
2542 2568
2543 const auto function = [this, &keys, &pdm] { 2569 const auto function = [this, &keys, &pdm] {
2544 keys.PopulateFromPartitionData(pdm); 2570 keys.PopulateFromPartitionData(pdm);
@@ -2870,7 +2896,7 @@ int main(int argc, char* argv[]) {
2870 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". 2896 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
2871 // But since we require the working directory to be the executable path for the location of 2897 // But since we require the working directory to be the executable path for the location of
2872 // the user folder in the Qt Frontend, we need to cd into that working directory 2898 // the user folder in the Qt Frontend, we need to cd into that working directory
2873 const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; 2899 const std::string bin_path = Common::FS::GetBundleDirectory() + DIR_SEP + "..";
2874 chdir(bin_path.c_str()); 2900 chdir(bin_path.c_str());
2875#endif 2901#endif
2876 2902
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 73a44a3bf..01f9131e5 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -198,7 +198,8 @@ private slots:
198 void OnOpenFAQ(); 198 void OnOpenFAQ();
199 /// Called whenever a user selects a game in the game list widget. 199 /// Called whenever a user selects a game in the game list widget.
200 void OnGameListLoadFile(QString game_path); 200 void OnGameListLoadFile(QString game_path);
201 void OnGameListOpenFolder(GameListOpenTarget target, const std::string& game_path); 201 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target,
202 const std::string& game_path);
202 void OnTransferableShaderCacheOpenFile(u64 program_id); 203 void OnTransferableShaderCacheOpenFile(u64 program_id);
203 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 204 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
204 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target); 205 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target);
@@ -216,6 +217,7 @@ private slots:
216 void OnMenuInstallToNAND(); 217 void OnMenuInstallToNAND();
217 void OnMenuRecentFile(); 218 void OnMenuRecentFile();
218 void OnConfigure(); 219 void OnConfigure();
220 void OnConfigurePerGame();
219 void OnLoadAmiibo(); 221 void OnLoadAmiibo();
220 void OnOpenYuzuFolder(); 222 void OnOpenYuzuFolder();
221 void OnAbout(); 223 void OnAbout();
@@ -249,6 +251,7 @@ private:
249 void ShowMouseCursor(); 251 void ShowMouseCursor();
250 void OpenURL(const QUrl& url); 252 void OpenURL(const QUrl& url);
251 void LoadTranslation(); 253 void LoadTranslation();
254 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
252 255
253 Ui::MainWindow ui; 256 Ui::MainWindow ui;
254 257
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index c3a1d715e..87ea985d8 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -81,6 +81,7 @@
81 <addaction name="action_Restart"/> 81 <addaction name="action_Restart"/>
82 <addaction name="separator"/> 82 <addaction name="separator"/>
83 <addaction name="action_Configure"/> 83 <addaction name="action_Configure"/>
84 <addaction name="action_Configure_Current_Game"/>
84 </widget> 85 </widget>
85 <widget class="QMenu" name="menu_View"> 86 <widget class="QMenu" name="menu_View">
86 <property name="title"> 87 <property name="title">
@@ -287,6 +288,14 @@
287 <string>Capture Screenshot</string> 288 <string>Capture Screenshot</string>
288 </property> 289 </property>
289 </action> 290 </action>
291 <action name="action_Configure_Current_Game">
292 <property name="enabled">
293 <bool>false</bool>
294 </property>
295 <property name="text">
296 <string>Configure Current Game..</string>
297 </property>
298 </action>
290 </widget> 299 </widget>
291 <resources/> 300 <resources/>
292 <connections/> 301 <connections/>
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c2a2982fb..8a63fd191 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -16,9 +16,11 @@
16#include "yuzu_cmd/config.h" 16#include "yuzu_cmd/config.h"
17#include "yuzu_cmd/default_ini.h" 17#include "yuzu_cmd/default_ini.h"
18 18
19namespace FS = Common::FS;
20
19Config::Config() { 21Config::Config() {
20 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 22 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
21 sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini"; 23 sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-config.ini";
22 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); 24 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
23 25
24 Reload(); 26 Reload();
@@ -31,8 +33,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
31 if (sdl2_config->ParseError() < 0) { 33 if (sdl2_config->ParseError() < 0) {
32 if (retry) { 34 if (retry) {
33 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); 35 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
34 FileUtil::CreateFullPath(location); 36 FS::CreateFullPath(location);
35 FileUtil::WriteStringToFile(true, location, default_contents); 37 FS::WriteStringToFile(true, location, default_contents);
36 sdl2_config = std::make_unique<INIReader>(location); // Reopen file 38 sdl2_config = std::make_unique<INIReader>(location); // Reopen file
37 39
38 return LoadINI(default_contents, false); 40 return LoadINI(default_contents, false);
@@ -315,21 +317,21 @@ void Config::ReadValues() {
315 // Data Storage 317 // Data Storage
316 Settings::values.use_virtual_sd = 318 Settings::values.use_virtual_sd =
317 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); 319 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
318 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, 320 FS::GetUserPath(
319 sdl2_config->Get("Data Storage", "nand_directory", 321 FS::UserPath::NANDDir,
320 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 322 sdl2_config->Get("Data Storage", "nand_directory", FS::GetUserPath(FS::UserPath::NANDDir)));
321 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, 323 FS::GetUserPath(
322 sdl2_config->Get("Data Storage", "sdmc_directory", 324 FS::UserPath::SDMCDir,
323 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 325 sdl2_config->Get("Data Storage", "sdmc_directory", FS::GetUserPath(FS::UserPath::SDMCDir)));
324 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, 326 FS::GetUserPath(
325 sdl2_config->Get("Data Storage", "load_directory", 327 FS::UserPath::LoadDir,
326 FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); 328 sdl2_config->Get("Data Storage", "load_directory", FS::GetUserPath(FS::UserPath::LoadDir)));
327 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, 329 FS::GetUserPath(
328 sdl2_config->Get("Data Storage", "dump_directory", 330 FS::UserPath::DumpDir,
329 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); 331 sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir)));
330 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, 332 FS::GetUserPath(FS::UserPath::CacheDir,
331 sdl2_config->Get("Data Storage", "cache_directory", 333 sdl2_config->Get("Data Storage", "cache_directory",
332 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); 334 FS::GetUserPath(FS::UserPath::CacheDir)));
333 Settings::values.gamecard_inserted = 335 Settings::values.gamecard_inserted =
334 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); 336 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
335 Settings::values.gamecard_current_game = 337 Settings::values.gamecard_current_game =
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 512b060a7..8efe49390 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -82,8 +82,8 @@ static void InitializeLogging() {
82 82
83 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); 83 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
84 84
85 const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); 85 const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
86 FileUtil::CreateFullPath(log_dir); 86 Common::FS::CreateFullPath(log_dir);
87 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); 87 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
88#ifdef _WIN32 88#ifdef _WIN32
89 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); 89 Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -229,7 +229,7 @@ int main(int argc, char** argv) {
229 } 229 }
230 } 230 }
231 231
232 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); 232 system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
233 233
234 // Core is loaded, start the GPU (makes the GPU contexts current to this thread) 234 // Core is loaded, start the GPU (makes the GPU contexts current to this thread)
235 system.GPU().Start(); 235 system.GPU().Start();
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index acb22885e..74022af23 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -15,10 +15,11 @@
15#include "yuzu_tester/config.h" 15#include "yuzu_tester/config.h"
16#include "yuzu_tester/default_ini.h" 16#include "yuzu_tester/default_ini.h"
17 17
18namespace FS = Common::FS;
19
18Config::Config() { 20Config::Config() {
19 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 21 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
20 sdl2_config_loc = 22 sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini";
21 FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini";
22 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); 23 sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
23 24
24 Reload(); 25 Reload();
@@ -31,8 +32,8 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
31 if (sdl2_config->ParseError() < 0) { 32 if (sdl2_config->ParseError() < 0) {
32 if (retry) { 33 if (retry) {
33 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); 34 LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
34 FileUtil::CreateFullPath(location); 35 FS::CreateFullPath(location);
35 FileUtil::WriteStringToFile(true, default_contents, location); 36 FS::WriteStringToFile(true, default_contents, location);
36 sdl2_config = std::make_unique<INIReader>(location); // Reopen file 37 sdl2_config = std::make_unique<INIReader>(location); // Reopen file
37 38
38 return LoadINI(default_contents, false); 39 return LoadINI(default_contents, false);
@@ -87,12 +88,12 @@ void Config::ReadValues() {
87 // Data Storage 88 // Data Storage
88 Settings::values.use_virtual_sd = 89 Settings::values.use_virtual_sd =
89 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); 90 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
90 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, 91 FS::GetUserPath(Common::FS::UserPath::NANDDir,
91 sdl2_config->Get("Data Storage", "nand_directory", 92 sdl2_config->Get("Data Storage", "nand_directory",
92 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); 93 Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
93 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, 94 FS::GetUserPath(Common::FS::UserPath::SDMCDir,
94 sdl2_config->Get("Data Storage", "sdmc_directory", 95 sdl2_config->Get("Data Storage", "sdmc_directory",
95 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 96 Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
96 97
97 // System 98 // System
98 Settings::values.current_user = std::clamp<int>( 99 Settings::values.current_user = std::clamp<int>(
diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp
index 083667baf..7acf0caad 100644
--- a/src/yuzu_tester/yuzu.cpp
+++ b/src/yuzu_tester/yuzu.cpp
@@ -79,8 +79,8 @@ static void InitializeLogging(bool console) {
79 if (console) 79 if (console)
80 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); 80 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
81 81
82 const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); 82 const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
83 FileUtil::CreateFullPath(log_dir); 83 Common::FS::CreateFullPath(log_dir);
84 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); 84 Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
85#ifdef _WIN32 85#ifdef _WIN32
86 Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); 86 Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
@@ -251,7 +251,8 @@ int main(int argc, char** argv) {
251 251
252 Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback); 252 Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback);
253 253
254 system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester"); 254 system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend",
255 "SDLHideTester");
255 256
256 system.GPU().Start(); 257 system.GPU().Start();
257 system.Renderer().Rasterizer().LoadDiskResources(); 258 system.Renderer().Rasterizer().LoadDiskResources();