summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt21
-rw-r--r--src/common/alignment.h10
-rw-r--r--src/common/file_util.cpp3
-rw-r--r--src/common/scm_rev.cpp.in6
-rw-r--r--src/common/scm_rev.h3
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/core.cpp12
-rw-r--r--src/core/core.h11
-rw-r--r--src/core/crypto/key_manager.cpp2
-rw-r--r--src/core/file_sys/bis_factory.cpp5
-rw-r--r--src/core/file_sys/bis_factory.h2
-rw-r--r--src/core/file_sys/romfs_factory.cpp4
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp5
-rw-r--r--src/core/file_sys/vfs_libzip.cpp79
-rw-r--r--src/core/file_sys/vfs_libzip.h13
-rw-r--r--src/core/gdbstub/gdbstub.cpp3
-rw-r--r--src/core/hle/kernel/handle_table.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp69
-rw-r--r--src/core/hle/service/am/am.h5
-rw-r--r--src/core/hle/service/am/applets/applets.cpp4
-rw-r--r--src/core/hle/service/am/applets/applets.h2
-rw-r--r--src/core/hle/service/apm/controller.cpp3
-rw-r--r--src/core/hle/service/apm/controller.h6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp137
-rw-r--r--src/core/hle/service/bcat/backend/backend.h150
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp504
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h58
-rw-r--r--src/core/hle/service/bcat/bcat.cpp9
-rw-r--r--src/core/hle/service/bcat/bcat.h7
-rw-r--r--src/core/hle/service/bcat/module.cpp561
-rw-r--r--src/core/hle/service/bcat/module.h31
-rw-r--r--src/core/hle/service/fatal/fatal.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp29
-rw-r--r--src/core/hle/service/filesystem/filesystem.h10
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp2
-rw-r--r--src/core/hle/service/friend/friend.cpp1
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp5
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h1
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h1
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h1
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h1
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp18
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h1
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp19
-rw-r--r--src/core/hle/service/hid/hid.h1
-rw-r--r--src/core/hle/service/ldr/ldr.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp.cpp9
-rw-r--r--src/core/hle/service/nifm/nifm.cpp13
-rw-r--r--src/core/hle/service/ns/pl_u.cpp8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp2
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/memory.cpp10
-rw-r--r--src/core/settings.cpp2
-rw-r--r--src/core/settings.h4
-rw-r--r--src/video_core/CMakeLists.txt6
-rw-r--r--src/video_core/engines/shader_bytecode.h8
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp446
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp50
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp374
-rw-r--r--src/video_core/shader/ast.cpp738
-rw-r--r--src/video_core/shader/ast.h400
-rw-r--r--src/video_core/shader/compiler_settings.cpp26
-rw-r--r--src/video_core/shader/compiler_settings.h26
-rw-r--r--src/video_core/shader/control_flow.cpp155
-rw-r--r--src/video_core/shader/control_flow.h14
-rw-r--r--src/video_core/shader/decode.cpp170
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp5
-rw-r--r--src/video_core/shader/decode/image.cpp137
-rw-r--r--src/video_core/shader/expr.cpp93
-rw-r--r--src/video_core/shader/expr.h139
-rw-r--r--src/video_core/shader/node.h46
-rw-r--r--src/video_core/shader/shader_ir.cpp12
-rw-r--r--src/video_core/shader/shader_ir.h42
-rw-r--r--src/video_core/texture_cache/texture_cache.h146
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp21
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_service.cpp138
-rw-r--r--src/yuzu/configuration/configure_service.h34
-rw-r--r--src/yuzu/configuration/configure_service.ui124
-rw-r--r--src/yuzu/game_list.cpp8
-rw-r--r--src/yuzu/game_list_p.h9
-rw-r--r--src/yuzu/game_list_worker.cpp12
-rw-r--r--src/yuzu/game_list_worker.h5
-rw-r--r--src/yuzu/main.cpp21
-rw-r--r--src/yuzu/uisettings.cpp2
-rw-r--r--src/yuzu/uisettings.h2
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp13
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h3
-rw-r--r--src/yuzu_cmd/yuzu.cpp4
-rw-r--r--src/yuzu_tester/config.cpp1
115 files changed, 4885 insertions, 575 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 01abdb3bb..906c486fd 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -10,13 +10,28 @@ if (DEFINED ENV{CI})
10 elseif(DEFINED ENV{APPVEYOR}) 10 elseif(DEFINED ENV{APPVEYOR})
11 set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) 11 set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
12 set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) 12 set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
13 elseif(DEFINED ENV{AZURE})
14 set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME})
15 set(BUILD_TAG $ENV{AZURE_REPO_TAG})
13 endif() 16 endif()
14endif() 17endif()
18if (DEFINED ENV{TITLEBARFORMATIDLE})
19 set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE})
20endif ()
21if (DEFINED ENV{TITLEBARFORMATRUNNING})
22 set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING})
23endif ()
24if (DEFINED ENV{DISPLAYVERSION})
25 set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
26endif ()
15add_custom_command(OUTPUT scm_rev.cpp 27add_custom_command(OUTPUT scm_rev.cpp
16 COMMAND ${CMAKE_COMMAND} 28 COMMAND ${CMAKE_COMMAND}
17 -DSRC_DIR="${CMAKE_SOURCE_DIR}" 29 -DSRC_DIR="${CMAKE_SOURCE_DIR}"
18 -DBUILD_REPOSITORY="${BUILD_REPOSITORY}" 30 -DBUILD_REPOSITORY="${BUILD_REPOSITORY}"
31 -DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}"
32 -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}"
19 -DBUILD_TAG="${BUILD_TAG}" 33 -DBUILD_TAG="${BUILD_TAG}"
34 -DBUILD_ID="${DISPLAY_VERSION}"
20 -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" 35 -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
21 DEPENDS 36 DEPENDS
22 # WARNING! It was too much work to try and make a common location for this list, 37 # WARNING! It was too much work to try and make a common location for this list,
@@ -57,9 +72,15 @@ add_custom_command(OUTPUT scm_rev.cpp
57 "${VIDEO_CORE}/shader/decode/video.cpp" 72 "${VIDEO_CORE}/shader/decode/video.cpp"
58 "${VIDEO_CORE}/shader/decode/warp.cpp" 73 "${VIDEO_CORE}/shader/decode/warp.cpp"
59 "${VIDEO_CORE}/shader/decode/xmad.cpp" 74 "${VIDEO_CORE}/shader/decode/xmad.cpp"
75 "${VIDEO_CORE}/shader/ast.cpp"
76 "${VIDEO_CORE}/shader/ast.h"
60 "${VIDEO_CORE}/shader/control_flow.cpp" 77 "${VIDEO_CORE}/shader/control_flow.cpp"
61 "${VIDEO_CORE}/shader/control_flow.h" 78 "${VIDEO_CORE}/shader/control_flow.h"
79 "${VIDEO_CORE}/shader/compiler_settings.cpp"
80 "${VIDEO_CORE}/shader/compiler_settings.h"
62 "${VIDEO_CORE}/shader/decode.cpp" 81 "${VIDEO_CORE}/shader/decode.cpp"
82 "${VIDEO_CORE}/shader/expr.cpp"
83 "${VIDEO_CORE}/shader/expr.h"
63 "${VIDEO_CORE}/shader/node.h" 84 "${VIDEO_CORE}/shader/node.h"
64 "${VIDEO_CORE}/shader/node_helper.cpp" 85 "${VIDEO_CORE}/shader/node_helper.cpp"
65 "${VIDEO_CORE}/shader/node_helper.h" 86 "${VIDEO_CORE}/shader/node_helper.h"
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 88d5d3a65..cdd4833f8 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -51,7 +51,17 @@ public:
51 using reference = T&; 51 using reference = T&;
52 using const_reference = const T&; 52 using const_reference = const T&;
53 53
54 using propagate_on_container_copy_assignment = std::true_type;
55 using propagate_on_container_move_assignment = std::true_type;
56 using propagate_on_container_swap = std::true_type;
57 using is_always_equal = std::true_type;
58
54public: 59public:
60 constexpr AlignmentAllocator() noexcept = default;
61
62 template <typename T2>
63 constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {}
64
55 pointer address(reference r) noexcept { 65 pointer address(reference r) noexcept {
56 return std::addressof(r); 66 return std::addressof(r);
57 } 67 }
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 2d9374783..41167f57a 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -713,7 +713,6 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
713 case UserPath::RootDir: 713 case UserPath::RootDir:
714 user_path = paths[UserPath::RootDir] + DIR_SEP; 714 user_path = paths[UserPath::RootDir] + DIR_SEP;
715 break; 715 break;
716
717 case UserPath::UserDir: 716 case UserPath::UserDir:
718 user_path = paths[UserPath::RootDir] + DIR_SEP; 717 user_path = paths[UserPath::RootDir] + DIR_SEP;
719 paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP; 718 paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP;
@@ -721,6 +720,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
721 paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP; 720 paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP;
722 paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP; 721 paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP;
723 break; 722 break;
723 default:
724 break;
724 } 725 }
725 } 726 }
726 727
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index d69038f65..5f126f324 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -11,6 +11,9 @@
11#define BUILD_DATE "@BUILD_DATE@" 11#define BUILD_DATE "@BUILD_DATE@"
12#define BUILD_FULLNAME "@BUILD_FULLNAME@" 12#define BUILD_FULLNAME "@BUILD_FULLNAME@"
13#define BUILD_VERSION "@BUILD_VERSION@" 13#define BUILD_VERSION "@BUILD_VERSION@"
14#define BUILD_ID "@BUILD_ID@"
15#define TITLE_BAR_FORMAT_IDLE "@TITLE_BAR_FORMAT_IDLE@"
16#define TITLE_BAR_FORMAT_RUNNING "@TITLE_BAR_FORMAT_RUNNING@"
14#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@" 17#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@"
15 18
16namespace Common { 19namespace Common {
@@ -22,6 +25,9 @@ const char g_build_name[] = BUILD_NAME;
22const char g_build_date[] = BUILD_DATE; 25const char g_build_date[] = BUILD_DATE;
23const char g_build_fullname[] = BUILD_FULLNAME; 26const char g_build_fullname[] = BUILD_FULLNAME;
24const char g_build_version[] = BUILD_VERSION; 27const char g_build_version[] = BUILD_VERSION;
28const char g_build_id[] = BUILD_ID;
29const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
30const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
25const char g_shader_cache_version[] = SHADER_CACHE_VERSION; 31const char g_shader_cache_version[] = SHADER_CACHE_VERSION;
26 32
27} // namespace 33} // namespace
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 666bf0367..563015ec9 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -13,6 +13,9 @@ extern const char g_build_name[];
13extern const char g_build_date[]; 13extern const char g_build_date[];
14extern const char g_build_fullname[]; 14extern const char g_build_fullname[];
15extern const char g_build_version[]; 15extern const char g_build_version[];
16extern const char g_build_id[];
17extern const char g_title_bar_format_idle[];
18extern const char g_title_bar_format_running[];
16extern const char g_shader_cache_version[]; 19extern const char g_shader_cache_version[];
17 20
18} // namespace Common 21} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index be3ffb779..3b1d72cf9 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,3 +1,9 @@
1if (YUZU_ENABLE_BOXCAT)
2 set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
3else()
4 set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
5endif()
6
1add_library(core STATIC 7add_library(core STATIC
2 arm/arm_interface.h 8 arm/arm_interface.h
3 arm/arm_interface.cpp 9 arm/arm_interface.cpp
@@ -82,6 +88,8 @@ add_library(core STATIC
82 file_sys/vfs_concat.h 88 file_sys/vfs_concat.h
83 file_sys/vfs_layered.cpp 89 file_sys/vfs_layered.cpp
84 file_sys/vfs_layered.h 90 file_sys/vfs_layered.h
91 file_sys/vfs_libzip.cpp
92 file_sys/vfs_libzip.h
85 file_sys/vfs_offset.cpp 93 file_sys/vfs_offset.cpp
86 file_sys/vfs_offset.h 94 file_sys/vfs_offset.h
87 file_sys/vfs_real.cpp 95 file_sys/vfs_real.cpp
@@ -241,6 +249,9 @@ add_library(core STATIC
241 hle/service/audio/errors.h 249 hle/service/audio/errors.h
242 hle/service/audio/hwopus.cpp 250 hle/service/audio/hwopus.cpp
243 hle/service/audio/hwopus.h 251 hle/service/audio/hwopus.h
252 hle/service/bcat/backend/backend.cpp
253 hle/service/bcat/backend/backend.h
254 ${BCAT_BOXCAT_ADDITIONAL_SOURCES}
244 hle/service/bcat/bcat.cpp 255 hle/service/bcat/bcat.cpp
245 hle/service/bcat/bcat.h 256 hle/service/bcat/bcat.h
246 hle/service/bcat/module.cpp 257 hle/service/bcat/module.cpp
@@ -501,6 +512,15 @@ create_target_directory_groups(core)
501 512
502target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 513target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
503target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives) 514target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
515
516if (YUZU_ENABLE_BOXCAT)
517 get_directory_property(OPENSSL_LIBS
518 DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
519 DEFINITION OPENSSL_LIBS)
520 target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
521 target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
522endif()
523
504if (ENABLE_WEB_SERVICE) 524if (ENABLE_WEB_SERVICE)
505 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) 525 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
506 target_link_libraries(core PRIVATE web_service) 526 target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/core.cpp b/src/core/core.cpp
index bf62085a8..4d0ac72a5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -112,7 +112,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
112} 112}
113struct System::Impl { 113struct System::Impl {
114 explicit Impl(System& system) 114 explicit Impl(System& system)
115 : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} 115 : kernel{system}, fs_controller{system}, cpu_core_manager{system},
116 applet_manager{system}, reporter{system} {}
116 117
117 Cpu& CurrentCpuCore() { 118 Cpu& CurrentCpuCore() {
118 return cpu_core_manager.GetCurrentCore(); 119 return cpu_core_manager.GetCurrentCore();
@@ -343,6 +344,7 @@ struct System::Impl {
343 Reporter reporter; 344 Reporter reporter;
344 std::unique_ptr<Memory::CheatEngine> cheat_engine; 345 std::unique_ptr<Memory::CheatEngine> cheat_engine;
345 std::unique_ptr<Tools::Freezer> memory_freezer; 346 std::unique_ptr<Tools::Freezer> memory_freezer;
347 std::array<u8, 0x20> build_id{};
346 348
347 /// Frontend applets 349 /// Frontend applets
348 Service::AM::Applets::AppletManager applet_manager; 350 Service::AM::Applets::AppletManager applet_manager;
@@ -651,6 +653,14 @@ bool System::GetExitLock() const {
651 return impl->exit_lock; 653 return impl->exit_lock;
652} 654}
653 655
656void System::SetCurrentProcessBuildID(const CurrentBuildProcessID& id) {
657 impl->build_id = id;
658}
659
660const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const {
661 return impl->build_id;
662}
663
654System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { 664System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
655 return impl->Init(*this, emu_window); 665 return impl->Init(*this, emu_window);
656} 666}
diff --git a/src/core/core.h b/src/core/core.h
index 0170e0b05..90e7ac607 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -8,7 +8,6 @@
8#include <memory> 8#include <memory>
9#include <string> 9#include <string>
10 10
11#include <map>
12#include "common/common_types.h" 11#include "common/common_types.h"
13#include "core/file_sys/vfs_types.h" 12#include "core/file_sys/vfs_types.h"
14#include "core/hle/kernel/object.h" 13#include "core/hle/kernel/object.h"
@@ -102,6 +101,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
102 101
103class System { 102class System {
104public: 103public:
104 using CurrentBuildProcessID = std::array<u8, 0x20>;
105
105 System(const System&) = delete; 106 System(const System&) = delete;
106 System& operator=(const System&) = delete; 107 System& operator=(const System&) = delete;
107 108
@@ -338,6 +339,10 @@ public:
338 339
339 bool GetExitLock() const; 340 bool GetExitLock() const;
340 341
342 void SetCurrentProcessBuildID(const CurrentBuildProcessID& id);
343
344 const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
345
341private: 346private:
342 System(); 347 System();
343 348
@@ -361,8 +366,4 @@ private:
361 static System s_instance; 366 static System s_instance;
362}; 367};
363 368
364inline Kernel::Process* CurrentProcess() {
365 return System::GetInstance().CurrentProcess();
366}
367
368} // namespace Core 369} // namespace Core
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 46aceec3d..222fc95ba 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -423,7 +423,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
423std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, 423std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
424 const RSAKeyPair<2048>& key) { 424 const RSAKeyPair<2048>& key) {
425 const auto issuer = ticket.GetData().issuer; 425 const auto issuer = ticket.GetData().issuer;
426 if (issuer == std::array<u8, 0x40>{}) 426 if (IsAllZeroArray(issuer))
427 return {}; 427 return {};
428 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { 428 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
429 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); 429 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 8f758d6d9..0af44f340 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -136,4 +136,9 @@ u64 BISFactory::GetFullNANDTotalSpace() const {
136 return static_cast<u64>(Settings::values.nand_total_size); 136 return static_cast<u64>(Settings::values.nand_total_size);
137} 137}
138 138
139VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
140 return GetOrCreateDirectoryRelative(nand_root,
141 fmt::format("/system/save/bcat/{:016X}", title_id));
142}
143
139} // namespace FileSys 144} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index bdfe728c9..8f0451c98 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -61,6 +61,8 @@ public:
61 u64 GetUserNANDTotalSpace() const; 61 u64 GetUserNANDTotalSpace() const;
62 u64 GetFullNANDTotalSpace() const; 62 u64 GetFullNANDTotalSpace() const;
63 63
64 VirtualDir GetBCATDirectory(u64 title_id) const;
65
64private: 66private:
65 VirtualDir nand_root; 67 VirtualDir nand_root;
66 VirtualDir load_root; 68 VirtualDir load_root;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 84cd4684c..4bd2e6183 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -35,11 +35,11 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
35 this->update_raw = std::move(update_raw); 35 this->update_raw = std::move(update_raw);
36} 36}
37 37
38ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const { 38ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
39 if (!updatable) 39 if (!updatable)
40 return MakeResult<VirtualFile>(file); 40 return MakeResult<VirtualFile>(file);
41 41
42 const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); 42 const PatchManager patch_manager(current_process_title_id);
43 return MakeResult<VirtualFile>( 43 return MakeResult<VirtualFile>(
44 patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); 44 patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
45} 45}
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index da63a313a..c5d40285c 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -33,7 +33,7 @@ public:
33 ~RomFSFactory(); 33 ~RomFSFactory();
34 34
35 void SetPackedUpdate(VirtualFile update_raw); 35 void SetPackedUpdate(VirtualFile update_raw);
36 ResultVal<VirtualFile> OpenCurrentProcess() const; 36 ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; 37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
38 38
39private: 39private:
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index f77cc02ac..fc8755c78 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -127,8 +127,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
127 u128 user_id, u64 save_id) { 127 u128 user_id, u64 save_id) {
128 // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should 128 // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
129 // be interpreted as the title id of the current process. 129 // be interpreted as the title id of the current process.
130 if (type == SaveDataType::SaveData && title_id == 0) 130 if (type == SaveDataType::SaveData && title_id == 0) {
131 title_id = Core::CurrentProcess()->GetTitleID(); 131 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
132 }
132 133
133 std::string out = GetSaveDataSpaceIdPath(space); 134 std::string out = GetSaveDataSpaceIdPath(space);
134 135
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
new file mode 100644
index 000000000..8bdaa7e4a
--- /dev/null
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -0,0 +1,79 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <string>
6#include <zip.h>
7#include "common/logging/backend.h"
8#include "core/file_sys/vfs.h"
9#include "core/file_sys/vfs_libzip.h"
10#include "core/file_sys/vfs_vector.h"
11
12namespace FileSys {
13
14VirtualDir ExtractZIP(VirtualFile file) {
15 zip_error_t error{};
16
17 const auto data = file->ReadAllBytes();
18 std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{
19 zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close};
20 if (src == nullptr)
21 return nullptr;
22
23 std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error),
24 zip_close};
25 if (zip == nullptr)
26 return nullptr;
27
28 std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>();
29
30 const auto num_entries = zip_get_num_entries(zip.get(), 0);
31
32 zip_stat_t stat{};
33 zip_stat_init(&stat);
34
35 for (std::size_t i = 0; i < num_entries; ++i) {
36 const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat);
37 if (stat_res == -1)
38 return nullptr;
39
40 const std::string name(stat.name);
41 if (name.empty())
42 continue;
43
44 if (name.back() != '/') {
45 std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file{
46 zip_fopen_index(zip.get(), i, 0), zip_fclose};
47
48 std::vector<u8> buf(stat.size);
49 if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size())
50 return nullptr;
51
52 const auto parts = FileUtil::SplitPathComponents(stat.name);
53 const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
54
55 std::shared_ptr<VectorVfsDirectory> dtrv = out;
56 for (std::size_t j = 0; j < parts.size() - 1; ++j) {
57 if (dtrv == nullptr)
58 return nullptr;
59 const auto subdir = dtrv->GetSubdirectory(parts[j]);
60 if (subdir == nullptr) {
61 const auto temp = std::make_shared<VectorVfsDirectory>(
62 std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parts[j]);
63 dtrv->AddDirectory(temp);
64 dtrv = temp;
65 } else {
66 dtrv = std::dynamic_pointer_cast<VectorVfsDirectory>(subdir);
67 }
68 }
69
70 if (dtrv == nullptr)
71 return nullptr;
72 dtrv->AddFile(new_file);
73 }
74 }
75
76 return out;
77}
78
79} // namespace FileSys
diff --git a/src/core/file_sys/vfs_libzip.h b/src/core/file_sys/vfs_libzip.h
new file mode 100644
index 000000000..f68af576a
--- /dev/null
+++ b/src/core/file_sys/vfs_libzip.h
@@ -0,0 +1,13 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/file_sys/vfs_types.h"
8
9namespace FileSys {
10
11VirtualDir ExtractZIP(VirtualFile zip);
12
13} // namespace FileSys
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index afa812598..db51d722f 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -641,7 +641,8 @@ static void HandleQuery() {
641 strlen("Xfer:features:read:target.xml:")) == 0) { 641 strlen("Xfer:features:read:target.xml:")) == 0) {
642 SendReply(target_xml); 642 SendReply(target_xml);
643 } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { 643 } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
644 const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress(); 644 const VAddr base_address =
645 Core::System::GetInstance().CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
645 std::string buffer = fmt::format("TextSeg={:0x}", base_address); 646 std::string buffer = fmt::format("TextSeg={:0x}", base_address);
646 SendReply(buffer.c_str()); 647 SendReply(buffer.c_str());
647 } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { 648 } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index bdfaa977f..2cc5d536b 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -103,7 +103,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
103 if (handle == CurrentThread) { 103 if (handle == CurrentThread) {
104 return GetCurrentThread(); 104 return GetCurrentThread();
105 } else if (handle == CurrentProcess) { 105 } else if (handle == CurrentProcess) {
106 return Core::CurrentProcess(); 106 return Core::System::GetInstance().CurrentProcess();
107 } 107 }
108 108
109 if (!IsValid(handle)) { 109 if (!IsValid(handle)) {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 797c9a06f..941ebc93a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -31,6 +31,7 @@
31#include "core/hle/service/am/tcap.h" 31#include "core/hle/service/am/tcap.h"
32#include "core/hle/service/apm/controller.h" 32#include "core/hle/service/apm/controller.h"
33#include "core/hle/service/apm/interface.h" 33#include "core/hle/service/apm/interface.h"
34#include "core/hle/service/bcat/backend/backend.h"
34#include "core/hle/service/filesystem/filesystem.h" 35#include "core/hle/service/filesystem/filesystem.h"
35#include "core/hle/service/ns/ns.h" 36#include "core/hle/service/ns/ns.h"
36#include "core/hle/service/nvflinger/nvflinger.h" 37#include "core/hle/service/nvflinger/nvflinger.h"
@@ -46,15 +47,20 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
46constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3}; 47constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
47constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; 48constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
48 49
49constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; 50enum class LaunchParameterKind : u32 {
51 ApplicationSpecific = 1,
52 AccountPreselectedUser = 2,
53};
54
55constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA;
50 56
51struct LaunchParameters { 57struct LaunchParameterAccountPreselectedUser {
52 u32_le magic; 58 u32_le magic;
53 u32_le is_account_selected; 59 u32_le is_account_selected;
54 u128 current_user; 60 u128 current_user;
55 INSERT_PADDING_BYTES(0x70); 61 INSERT_PADDING_BYTES(0x70);
56}; 62};
57static_assert(sizeof(LaunchParameters) == 0x88); 63static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88);
58 64
59IWindowController::IWindowController(Core::System& system_) 65IWindowController::IWindowController(Core::System& system_)
60 : ServiceFramework("IWindowController"), system{system_} { 66 : ServiceFramework("IWindowController"), system{system_} {
@@ -1128,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
1128} 1134}
1129 1135
1130void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { 1136void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
1131 LOG_DEBUG(Service_AM, "called"); 1137 IPC::RequestParser rp{ctx};
1138 const auto kind = rp.PopEnum<LaunchParameterKind>();
1132 1139
1133 LaunchParameters params{}; 1140 LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
1134 1141
1135 params.magic = POP_LAUNCH_PARAMETER_MAGIC; 1142 if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
1136 params.is_account_selected = 1; 1143 const auto backend = BCAT::CreateBackendFromSettings(
1144 [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
1145 const auto build_id_full = system.GetCurrentProcessBuildID();
1146 u64 build_id{};
1147 std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
1137 1148
1138 Account::ProfileManager profile_manager{}; 1149 const auto data =
1139 const auto uuid = profile_manager.GetUser(Settings::values.current_user); 1150 backend->GetLaunchParameter({system.CurrentProcess()->GetTitleID(), build_id});
1140 ASSERT(uuid);
1141 params.current_user = uuid->uuid;
1142 1151
1143 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 1152 if (data.has_value()) {
1153 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1154 rb.Push(RESULT_SUCCESS);
1155 rb.PushIpcInterface<AM::IStorage>(*data);
1156 launch_popped_application_specific = true;
1157 return;
1158 }
1159 } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
1160 !launch_popped_account_preselect) {
1161 LaunchParameterAccountPreselectedUser params{};
1144 1162
1145 rb.Push(RESULT_SUCCESS); 1163 params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
1164 params.is_account_selected = 1;
1146 1165
1147 std::vector<u8> buffer(sizeof(LaunchParameters)); 1166 Account::ProfileManager profile_manager{};
1148 std::memcpy(buffer.data(), &params, buffer.size()); 1167 const auto uuid = profile_manager.GetUser(Settings::values.current_user);
1168 ASSERT(uuid);
1169 params.current_user = uuid->uuid;
1149 1170
1150 rb.PushIpcInterface<AM::IStorage>(buffer); 1171 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1172
1173 rb.Push(RESULT_SUCCESS);
1174
1175 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
1176 std::memcpy(buffer.data(), &params, buffer.size());
1177
1178 rb.PushIpcInterface<AM::IStorage>(buffer);
1179 launch_popped_account_preselect = true;
1180 return;
1181 }
1182
1183 LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
1184 IPC::ResponseBuilder rb{ctx, 2};
1185 rb.Push(ERR_NO_DATA_IN_CHANNEL);
1151} 1186}
1152 1187
1153void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( 1188void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
@@ -1165,7 +1200,7 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
1165 LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]); 1200 LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
1166 1201
1167 FileSys::SaveDataDescriptor descriptor{}; 1202 FileSys::SaveDataDescriptor descriptor{};
1168 descriptor.title_id = Core::CurrentProcess()->GetTitleID(); 1203 descriptor.title_id = system.CurrentProcess()->GetTitleID();
1169 descriptor.user_id = user_id; 1204 descriptor.user_id = user_id;
1170 descriptor.type = FileSys::SaveDataType::SaveData; 1205 descriptor.type = FileSys::SaveDataType::SaveData;
1171 const auto res = system.GetFileSystemController().CreateSaveData( 1206 const auto res = system.GetFileSystemController().CreateSaveData(
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index a3baeb673..ccd053c13 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -147,6 +147,7 @@ private:
147 void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); 147 void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
148 void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); 148 void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
149 149
150 Core::System& system;
150 std::shared_ptr<NVFlinger::NVFlinger> nvflinger; 151 std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
151 Kernel::EventPair launchable_event; 152 Kernel::EventPair launchable_event;
152 Kernel::EventPair accumulated_suspended_tick_changed_event; 153 Kernel::EventPair accumulated_suspended_tick_changed_event;
@@ -154,8 +155,6 @@ private:
154 u32 idle_time_detection_extension = 0; 155 u32 idle_time_detection_extension = 0;
155 u64 num_fatal_sections_entered = 0; 156 u64 num_fatal_sections_entered = 0;
156 bool is_auto_sleep_disabled = false; 157 bool is_auto_sleep_disabled = false;
157
158 Core::System& system;
159}; 158};
160 159
161class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 160class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
@@ -255,6 +254,8 @@ private:
255 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); 254 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
256 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); 255 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
257 256
257 bool launch_popped_application_specific = false;
258 bool launch_popped_account_preselect = false;
258 Kernel::EventPair gpu_error_detected_event; 259 Kernel::EventPair gpu_error_detected_event;
259 Core::System& system; 260 Core::System& system;
260}; 261};
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index d2e35362f..720fe766f 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -157,6 +157,10 @@ AppletManager::AppletManager(Core::System& system_) : system{system_} {}
157 157
158AppletManager::~AppletManager() = default; 158AppletManager::~AppletManager() = default;
159 159
160const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
161 return frontend;
162}
163
160void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { 164void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
161 if (set.parental_controls != nullptr) 165 if (set.parental_controls != nullptr)
162 frontend.parental_controls = std::move(set.parental_controls); 166 frontend.parental_controls = std::move(set.parental_controls);
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 764c3418c..226be88b1 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -190,6 +190,8 @@ public:
190 explicit AppletManager(Core::System& system_); 190 explicit AppletManager(Core::System& system_);
191 ~AppletManager(); 191 ~AppletManager();
192 192
193 const AppletFrontendSet& GetAppletFrontendSet() const;
194
193 void SetAppletFrontendSet(AppletFrontendSet set); 195 void SetAppletFrontendSet(AppletFrontendSet set);
194 void SetDefaultAppletFrontendSet(); 196 void SetDefaultAppletFrontendSet();
195 void SetDefaultAppletsIfMissing(); 197 void SetDefaultAppletsIfMissing();
diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp
index 4376612eb..073d0f6fa 100644
--- a/src/core/hle/service/apm/controller.cpp
+++ b/src/core/hle/service/apm/controller.cpp
@@ -13,7 +13,7 @@ constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION =
13 PerformanceConfiguration::Config7; 13 PerformanceConfiguration::Config7;
14 14
15Controller::Controller(Core::Timing::CoreTiming& core_timing) 15Controller::Controller(Core::Timing::CoreTiming& core_timing)
16 : core_timing(core_timing), configs{ 16 : core_timing{core_timing}, configs{
17 {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION}, 17 {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
18 {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION}, 18 {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
19 } {} 19 } {}
@@ -63,6 +63,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
63void Controller::SetClockSpeed(u32 mhz) { 63void Controller::SetClockSpeed(u32 mhz) {
64 LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); 64 LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
65 // TODO(DarkLordZach): Actually signal core_timing to change clock speed. 65 // TODO(DarkLordZach): Actually signal core_timing to change clock speed.
66 // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
66} 67}
67 68
68} // namespace Service::APM 69} // namespace Service::APM
diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h
index 8ac80eaea..454caa6eb 100644
--- a/src/core/hle/service/apm/controller.h
+++ b/src/core/hle/service/apm/controller.h
@@ -50,7 +50,7 @@ enum class PerformanceMode : u8 {
50// system during times of high load -- this simply maps to different PerformanceConfigs to use. 50// system during times of high load -- this simply maps to different PerformanceConfigs to use.
51class Controller { 51class Controller {
52public: 52public:
53 Controller(Core::Timing::CoreTiming& core_timing); 53 explicit Controller(Core::Timing::CoreTiming& core_timing);
54 ~Controller(); 54 ~Controller();
55 55
56 void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config); 56 void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config);
@@ -62,9 +62,9 @@ public:
62private: 62private:
63 void SetClockSpeed(u32 mhz); 63 void SetClockSpeed(u32 mhz);
64 64
65 std::map<PerformanceMode, PerformanceConfiguration> configs; 65 [[maybe_unused]] Core::Timing::CoreTiming& core_timing;
66 66
67 Core::Timing::CoreTiming& core_timing; 67 std::map<PerformanceMode, PerformanceConfiguration> configs;
68}; 68};
69 69
70} // namespace Service::APM 70} // namespace Service::APM
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index fb84a8f13..9afefb5c6 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -205,7 +205,7 @@ private:
205 AudioCore::StreamPtr stream; 205 AudioCore::StreamPtr stream;
206 std::string device_name; 206 std::string device_name;
207 207
208 AudoutParams audio_params{}; 208 [[maybe_unused]] AudoutParams audio_params {};
209 209
210 /// This is the event handle used to check if the audio buffer was released 210 /// This is the event handle used to check if the audio buffer was released
211 Kernel::EventPair buffer_event; 211 Kernel::EventPair buffer_event;
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
new file mode 100644
index 000000000..9d6946bc5
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -0,0 +1,137 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/hex_util.h"
6#include "common/logging/log.h"
7#include "core/core.h"
8#include "core/hle/lock.h"
9#include "core/hle/service/bcat/backend/backend.h"
10
11namespace Service::BCAT {
12
13ProgressServiceBackend::ProgressServiceBackend(std::string_view event_name) {
14 auto& kernel{Core::System::GetInstance().Kernel()};
15 event = Kernel::WritableEvent::CreateEventPair(
16 kernel, Kernel::ResetType::Automatic,
17 std::string("ProgressServiceBackend:UpdateEvent:").append(event_name));
18}
19
20Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const {
21 return event.readable;
22}
23
24DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
25 return impl;
26}
27
28void ProgressServiceBackend::SetNeedHLELock(bool need) {
29 need_hle_lock = need;
30}
31
32void ProgressServiceBackend::SetTotalSize(u64 size) {
33 impl.total_bytes = size;
34 SignalUpdate();
35}
36
37void ProgressServiceBackend::StartConnecting() {
38 impl.status = DeliveryCacheProgressImpl::Status::Connecting;
39 SignalUpdate();
40}
41
42void ProgressServiceBackend::StartProcessingDataList() {
43 impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
44 SignalUpdate();
45}
46
47void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
48 std::string_view file_name, u64 file_size) {
49 impl.status = DeliveryCacheProgressImpl::Status::Downloading;
50 impl.current_downloaded_bytes = 0;
51 impl.current_total_bytes = file_size;
52 std::memcpy(impl.current_directory.data(), dir_name.data(),
53 std::min<u64>(dir_name.size(), 0x31ull));
54 std::memcpy(impl.current_file.data(), file_name.data(),
55 std::min<u64>(file_name.size(), 0x31ull));
56 SignalUpdate();
57}
58
59void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
60 impl.current_downloaded_bytes = downloaded;
61 SignalUpdate();
62}
63
64void ProgressServiceBackend::FinishDownloadingFile() {
65 impl.total_downloaded_bytes += impl.current_total_bytes;
66 SignalUpdate();
67}
68
69void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
70 impl.status = DeliveryCacheProgressImpl::Status::Committing;
71 impl.current_file.fill(0);
72 impl.current_downloaded_bytes = 0;
73 impl.current_total_bytes = 0;
74 std::memcpy(impl.current_directory.data(), dir_name.data(),
75 std::min<u64>(dir_name.size(), 0x31ull));
76 SignalUpdate();
77}
78
79void ProgressServiceBackend::FinishDownload(ResultCode result) {
80 impl.total_downloaded_bytes = impl.total_bytes;
81 impl.status = DeliveryCacheProgressImpl::Status::Done;
82 impl.result = result;
83 SignalUpdate();
84}
85
86void ProgressServiceBackend::SignalUpdate() const {
87 if (need_hle_lock) {
88 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
89 event.writable->Signal();
90 } else {
91 event.writable->Signal();
92 }
93}
94
95Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
96
97Backend::~Backend() = default;
98
99NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {}
100
101NullBackend::~NullBackend() = default;
102
103bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
104 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
105 title.build_id);
106
107 progress.FinishDownload(RESULT_SUCCESS);
108 return true;
109}
110
111bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
112 ProgressServiceBackend& progress) {
113 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
114 title.build_id, name);
115
116 progress.FinishDownload(RESULT_SUCCESS);
117 return true;
118}
119
120bool NullBackend::Clear(u64 title_id) {
121 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
122
123 return true;
124}
125
126void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
127 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
128 Common::HexToString(passphrase));
129}
130
131std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
132 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
133 title.build_id);
134 return std::nullopt;
135}
136
137} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
new file mode 100644
index 000000000..51dbd3316
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -0,0 +1,150 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <optional>
9#include <string>
10#include <string_view>
11
12#include "common/common_types.h"
13#include "core/file_sys/vfs_types.h"
14#include "core/hle/kernel/readable_event.h"
15#include "core/hle/kernel/writable_event.h"
16#include "core/hle/result.h"
17
18namespace Service::BCAT {
19
20struct DeliveryCacheProgressImpl;
21
22using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
23using Passphrase = std::array<u8, 0x20>;
24
25struct TitleIDVersion {
26 u64 title_id;
27 u64 build_id;
28};
29
30using DirectoryName = std::array<char, 0x20>;
31using FileName = std::array<char, 0x20>;
32
33struct DeliveryCacheProgressImpl {
34 enum class Status : s32 {
35 None = 0x0,
36 Queued = 0x1,
37 Connecting = 0x2,
38 ProcessingDataList = 0x3,
39 Downloading = 0x4,
40 Committing = 0x5,
41 Done = 0x9,
42 };
43
44 Status status;
45 ResultCode result = RESULT_SUCCESS;
46 DirectoryName current_directory;
47 FileName current_file;
48 s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
49 s64 current_total_bytes; ///< Bytes total on current file.
50 s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
51 s64 total_bytes; ///< Bytes total on overall download.
52 INSERT_PADDING_BYTES(
53 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
54};
55static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
56 "DeliveryCacheProgressImpl has incorrect size.");
57
58// A class to manage the signalling to the game about BCAT download progress.
59// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
60class ProgressServiceBackend {
61 friend class IBcatService;
62
63public:
64 // Clients should call this with true if any of the functions are going to be called from a
65 // non-HLE thread and this class need to lock the hle mutex. (default is false)
66 void SetNeedHLELock(bool need);
67
68 // Sets the number of bytes total in the entire download.
69 void SetTotalSize(u64 size);
70
71 // Notifies the application that the backend has started connecting to the server.
72 void StartConnecting();
73 // Notifies the application that the backend has begun accumulating and processing metadata.
74 void StartProcessingDataList();
75
76 // Notifies the application that a file is starting to be downloaded.
77 void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
78 // Updates the progress of the current file to the size passed.
79 void UpdateFileProgress(u64 downloaded);
80 // Notifies the application that the current file has completed download.
81 void FinishDownloadingFile();
82
83 // Notifies the application that all files in this directory have completed and are being
84 // finalized.
85 void CommitDirectory(std::string_view dir_name);
86
87 // Notifies the application that the operation completed with result code result.
88 void FinishDownload(ResultCode result);
89
90private:
91 explicit ProgressServiceBackend(std::string_view event_name);
92
93 Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent() const;
94 DeliveryCacheProgressImpl& GetImpl();
95
96 void SignalUpdate() const;
97
98 DeliveryCacheProgressImpl impl{};
99 Kernel::EventPair event;
100 bool need_hle_lock = false;
101};
102
103// A class representing an abstract backend for BCAT functionality.
104class Backend {
105public:
106 explicit Backend(DirectoryGetter getter);
107 virtual ~Backend();
108
109 // Called when the backend is needed to synchronize the data for the game with title ID and
110 // version in title. A ProgressServiceBackend object is provided to alert the application of
111 // status.
112 virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
113 // Very similar to Synchronize, but only for the directory provided. Backends should not alter
114 // the data for any other directories.
115 virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
116 ProgressServiceBackend& progress) = 0;
117
118 // Removes all cached data associated with title id provided.
119 virtual bool Clear(u64 title_id) = 0;
120
121 // Sets the BCAT Passphrase to be used with the associated title ID.
122 virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
123
124 // Gets the launch parameter used by AM associated with the title ID and version provided.
125 virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
126
127protected:
128 DirectoryGetter dir_getter;
129};
130
131// A backend of BCAT that provides no operation.
132class NullBackend : public Backend {
133public:
134 explicit NullBackend(DirectoryGetter getter);
135 ~NullBackend() override;
136
137 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
138 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
139 ProgressServiceBackend& progress) override;
140
141 bool Clear(u64 title_id) override;
142
143 void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
144
145 std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
146};
147
148std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
149
150} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
new file mode 100644
index 000000000..64022982b
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -0,0 +1,504 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <fmt/ostream.h>
6#include <httplib.h>
7#include <json.hpp>
8#include <mbedtls/sha256.h>
9#include "common/hex_util.h"
10#include "common/logging/backend.h"
11#include "common/logging/log.h"
12#include "core/core.h"
13#include "core/file_sys/vfs.h"
14#include "core/file_sys/vfs_libzip.h"
15#include "core/file_sys/vfs_vector.h"
16#include "core/frontend/applets/error.h"
17#include "core/hle/service/am/applets/applets.h"
18#include "core/hle/service/bcat/backend/boxcat.h"
19#include "core/settings.h"
20
21namespace {
22
23// Prevents conflicts with windows macro called CreateFile
24FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
25 return dir->CreateFile(name);
26}
27
28// Prevents conflicts with windows macro called DeleteFile
29bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
30 return dir->DeleteFile(name);
31}
32
33} // Anonymous namespace
34
35namespace Service::BCAT {
36
37constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
38
39constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
40
41// Formatted using fmt with arg[0] = hex title id
42constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
43constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
44
45constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
46
47constexpr char BOXCAT_API_VERSION[] = "1";
48constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
49
50// HTTP status codes for Boxcat
51enum class ResponseStatus {
52 Ok = 200, ///< Operation completed successfully.
53 BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
54 NoUpdate = 304, ///< The digest provided would match the new data, no need to update.
55 NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation.
56 NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format
57 ///< issues or whatnot) and has no data.
58};
59
60enum class DownloadResult {
61 Success = 0,
62 NoResponse,
63 GeneralWebError,
64 NoMatchTitleId,
65 NoMatchBuildId,
66 InvalidContentType,
67 GeneralFSError,
68 BadClientVersion,
69};
70
71constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
72 "Success",
73 "There was no response from the server.",
74 "There was a general web error code returned from the server.",
75 "The title ID of the current game doesn't have a boxcat implementation. If you believe an "
76 "implementation should be added, contact yuzu support.",
77 "The build ID of the current version of the game is marked as incompatible with the current "
78 "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
79 "The content type of the web response was invalid.",
80 "There was a general filesystem error while saving the zip file.",
81 "The server is either too new or too old to serve the request. Try using the latest version of "
82 "an official release of yuzu.",
83};
84
85std::ostream& operator<<(std::ostream& os, DownloadResult result) {
86 return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
87}
88
89constexpr u32 PORT = 443;
90constexpr u32 TIMEOUT_SECONDS = 30;
91[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
92
93namespace {
94
95std::string GetBINFilePath(u64 title_id) {
96 return fmt::format("{}bcat/{:016X}/launchparam.bin",
97 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
98}
99
100std::string GetZIPFilePath(u64 title_id) {
101 return fmt::format("{}bcat/{:016X}/data.zip",
102 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
103}
104
105// If the error is something the user should know about (build ID mismatch, bad client version),
106// display an error.
107void HandleDownloadDisplayResult(DownloadResult res) {
108 if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
109 res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
110 res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
111 return;
112 }
113
114 const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()};
115 frontend.error->ShowCustomErrorText(
116 ResultCode(-1), "There was an error while attempting to use Boxcat.",
117 DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
118}
119
120bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
121 std::string_view dir_name, ProgressServiceBackend& progress,
122 std::size_t block_size = 0x1000) {
123 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
124 return false;
125 if (!dest->Resize(src->GetSize()))
126 return false;
127
128 progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
129
130 std::vector<u8> temp(std::min(block_size, src->GetSize()));
131 for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
132 const auto read = std::min(block_size, src->GetSize() - i);
133
134 if (src->Read(temp.data(), read, i) != read) {
135 return false;
136 }
137
138 if (dest->Write(temp.data(), read, i) != read) {
139 return false;
140 }
141
142 progress.UpdateFileProgress(i);
143 }
144
145 progress.FinishDownloadingFile();
146
147 return true;
148}
149
150bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
151 ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
152 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
153 return false;
154
155 for (const auto& file : src->GetFiles()) {
156 const auto out_file = VfsCreateFileWrap(dest, file->GetName());
157 if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
158 return false;
159 }
160 }
161 progress.CommitDirectory(src->GetName());
162
163 return true;
164}
165
166bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
167 ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
168 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
169 return false;
170
171 for (const auto& dir : src->GetSubdirectories()) {
172 const auto out = dest->CreateSubdirectory(dir->GetName());
173 if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
174 return false;
175 }
176 }
177
178 return true;
179}
180
181} // Anonymous namespace
182
183class Boxcat::Client {
184public:
185 Client(std::string path, u64 title_id, u64 build_id)
186 : path(std::move(path)), title_id(title_id), build_id(build_id) {}
187
188 DownloadResult DownloadDataZip() {
189 return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
190 "application/zip");
191 }
192
193 DownloadResult DownloadLaunchParam() {
194 return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
195 TIMEOUT_SECONDS / 3, "application/octet-stream");
196 }
197
198private:
199 DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
200 const std::string& content_type_name) {
201 if (client == nullptr) {
202 client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
203 }
204
205 httplib::Headers headers{
206 {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
207 {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
208 {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
209 };
210
211 if (FileUtil::Exists(path)) {
212 FileUtil::IOFile file{path, "rb"};
213 if (file.IsOpen()) {
214 std::vector<u8> bytes(file.GetSize());
215 file.ReadBytes(bytes.data(), bytes.size());
216 const auto digest = DigestFile(bytes);
217 headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
218 }
219 }
220
221 const auto response = client->Get(resolved_path.c_str(), headers);
222 if (response == nullptr)
223 return DownloadResult::NoResponse;
224
225 if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
226 return DownloadResult::Success;
227 if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
228 return DownloadResult::BadClientVersion;
229 if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
230 return DownloadResult::NoMatchTitleId;
231 if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
232 return DownloadResult::NoMatchBuildId;
233 if (response->status != static_cast<int>(ResponseStatus::Ok))
234 return DownloadResult::GeneralWebError;
235
236 const auto content_type = response->headers.find("content-type");
237 if (content_type == response->headers.end() ||
238 content_type->second.find(content_type_name) == std::string::npos) {
239 return DownloadResult::InvalidContentType;
240 }
241
242 FileUtil::CreateFullPath(path);
243 FileUtil::IOFile file{path, "wb"};
244 if (!file.IsOpen())
245 return DownloadResult::GeneralFSError;
246 if (!file.Resize(response->body.size()))
247 return DownloadResult::GeneralFSError;
248 if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
249 return DownloadResult::GeneralFSError;
250
251 return DownloadResult::Success;
252 }
253
254 using Digest = std::array<u8, 0x20>;
255 static Digest DigestFile(std::vector<u8> bytes) {
256 Digest out{};
257 mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0);
258 return out;
259 }
260
261 std::unique_ptr<httplib::Client> client;
262 std::string path;
263 u64 title_id;
264 u64 build_id;
265};
266
267Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
268
269Boxcat::~Boxcat() = default;
270
271void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
272 ProgressServiceBackend& progress,
273 std::optional<std::string> dir_name = {}) {
274 progress.SetNeedHLELock(true);
275
276 if (Settings::values.bcat_boxcat_local) {
277 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
278 const auto dir = dir_getter(title.title_id);
279 if (dir)
280 progress.SetTotalSize(dir->GetSize());
281 progress.FinishDownload(RESULT_SUCCESS);
282 return;
283 }
284
285 const auto zip_path{GetZIPFilePath(title.title_id)};
286 Boxcat::Client client{zip_path, title.title_id, title.build_id};
287
288 progress.StartConnecting();
289
290 const auto res = client.DownloadDataZip();
291 if (res != DownloadResult::Success) {
292 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
293
294 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
295 FileUtil::Delete(zip_path);
296 }
297
298 HandleDownloadDisplayResult(res);
299 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
300 return;
301 }
302
303 progress.StartProcessingDataList();
304
305 FileUtil::IOFile zip{zip_path, "rb"};
306 const auto size = zip.GetSize();
307 std::vector<u8> bytes(size);
308 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
309 LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
310 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
311 return;
312 }
313
314 const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
315 if (extracted == nullptr) {
316 LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
317 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
318 return;
319 }
320
321 if (dir_name == std::nullopt) {
322 progress.SetTotalSize(extracted->GetSize());
323
324 const auto target_dir = dir_getter(title.title_id);
325 if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
326 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
327 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
328 return;
329 }
330 } else {
331 const auto target_dir = dir_getter(title.title_id);
332 if (target_dir == nullptr) {
333 LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
334 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
335 return;
336 }
337
338 const auto target_sub = target_dir->GetSubdirectory(*dir_name);
339 const auto source_sub = extracted->GetSubdirectory(*dir_name);
340
341 progress.SetTotalSize(source_sub->GetSize());
342
343 std::vector<std::string> filenames;
344 {
345 const auto files = target_sub->GetFiles();
346 std::transform(files.begin(), files.end(), std::back_inserter(filenames),
347 [](const auto& vfile) { return vfile->GetName(); });
348 }
349
350 for (const auto& filename : filenames) {
351 VfsDeleteFileWrap(target_sub, filename);
352 }
353
354 if (target_sub == nullptr || source_sub == nullptr ||
355 !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
356 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
357 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
358 return;
359 }
360 }
361
362 progress.FinishDownload(RESULT_SUCCESS);
363}
364
365bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
366 is_syncing.exchange(true);
367 std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
368 .detach();
369 return true;
370}
371
372bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
373 ProgressServiceBackend& progress) {
374 is_syncing.exchange(true);
375 std::thread(
376 [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
377 .detach();
378 return true;
379}
380
381bool Boxcat::Clear(u64 title_id) {
382 if (Settings::values.bcat_boxcat_local) {
383 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
384 return true;
385 }
386
387 const auto dir = dir_getter(title_id);
388
389 std::vector<std::string> dirnames;
390
391 for (const auto& subdir : dir->GetSubdirectories())
392 dirnames.push_back(subdir->GetName());
393
394 for (const auto& subdir : dirnames) {
395 if (!dir->DeleteSubdirectoryRecursive(subdir))
396 return false;
397 }
398
399 return true;
400}
401
402void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
403 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
404 Common::HexToString(passphrase));
405}
406
407std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
408 const auto path{GetBINFilePath(title.title_id)};
409
410 if (Settings::values.bcat_boxcat_local) {
411 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
412 } else {
413 Boxcat::Client client{path, title.title_id, title.build_id};
414
415 const auto res = client.DownloadLaunchParam();
416 if (res != DownloadResult::Success) {
417 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
418
419 if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
420 FileUtil::Delete(path);
421 }
422
423 HandleDownloadDisplayResult(res);
424 return std::nullopt;
425 }
426 }
427
428 FileUtil::IOFile bin{path, "rb"};
429 const auto size = bin.GetSize();
430 std::vector<u8> bytes(size);
431 if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
432 LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
433 path);
434 return std::nullopt;
435 }
436
437 return bytes;
438}
439
440Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
441 std::map<std::string, EventStatus>& games) {
442 httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT),
443 static_cast<int>(TIMEOUT_SECONDS)};
444
445 httplib::Headers headers{
446 {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
447 {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
448 };
449
450 const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
451 if (response == nullptr)
452 return StatusResult::Offline;
453
454 if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
455 return StatusResult::BadClientVersion;
456
457 try {
458 nlohmann::json json = nlohmann::json::parse(response->body);
459
460 if (!json["online"].get<bool>())
461 return StatusResult::Offline;
462
463 if (json["global"].is_null())
464 global = std::nullopt;
465 else
466 global = json["global"].get<std::string>();
467
468 if (json["games"].is_array()) {
469 for (const auto object : json["games"]) {
470 if (object.is_object() && object.find("name") != object.end()) {
471 EventStatus detail{};
472 if (object["header"].is_string()) {
473 detail.header = object["header"].get<std::string>();
474 } else {
475 detail.header = std::nullopt;
476 }
477
478 if (object["footer"].is_string()) {
479 detail.footer = object["footer"].get<std::string>();
480 } else {
481 detail.footer = std::nullopt;
482 }
483
484 if (object["events"].is_array()) {
485 for (const auto& event : object["events"]) {
486 if (!event.is_string())
487 continue;
488 detail.events.push_back(event.get<std::string>());
489 }
490 }
491
492 games.insert_or_assign(object["name"], std::move(detail));
493 }
494 }
495 }
496
497 return StatusResult::Success;
498 } catch (const nlohmann::json::parse_error& error) {
499 LOG_ERROR(Service_BCAT, "{}", error.what());
500 return StatusResult::ParseError;
501 }
502}
503
504} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h
new file mode 100644
index 000000000..601151189
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.h
@@ -0,0 +1,58 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <map>
9#include <optional>
10#include "core/hle/service/bcat/backend/backend.h"
11
12namespace Service::BCAT {
13
14struct EventStatus {
15 std::optional<std::string> header;
16 std::optional<std::string> footer;
17 std::vector<std::string> events;
18};
19
20/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and
21/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
22class Boxcat final : public Backend {
23 friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
24 ProgressServiceBackend& progress,
25 std::optional<std::string> dir_name);
26
27public:
28 explicit Boxcat(DirectoryGetter getter);
29 ~Boxcat() override;
30
31 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
32 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
33 ProgressServiceBackend& progress) override;
34
35 bool Clear(u64 title_id) override;
36
37 void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
38
39 std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
40
41 enum class StatusResult {
42 Success,
43 Offline,
44 ParseError,
45 BadClientVersion,
46 };
47
48 static StatusResult GetStatus(std::optional<std::string>& global,
49 std::map<std::string, EventStatus>& games);
50
51private:
52 std::atomic_bool is_syncing{false};
53
54 class Client;
55 std::unique_ptr<Client> client;
56};
57
58} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 179aa4949..8bb2528c9 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -6,11 +6,16 @@
6 6
7namespace Service::BCAT { 7namespace Service::BCAT {
8 8
9BCAT::BCAT(std::shared_ptr<Module> module, const char* name) 9BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module,
10 : Module::Interface(std::move(module), name) { 10 FileSystem::FileSystemController& fsc, const char* name)
11 : Interface(system, std::move(module), fsc, name) {
12 // clang-format off
11 static const FunctionInfo functions[] = { 13 static const FunctionInfo functions[] = {
12 {0, &BCAT::CreateBcatService, "CreateBcatService"}, 14 {0, &BCAT::CreateBcatService, "CreateBcatService"},
15 {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
16 {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
13 }; 17 };
18 // clang-format on
14 RegisterHandlers(functions); 19 RegisterHandlers(functions);
15} 20}
16 21
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 802bd689a..6354465fc 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -6,11 +6,16 @@
6 6
7#include "core/hle/service/bcat/module.h" 7#include "core/hle/service/bcat/module.h"
8 8
9namespace Core {
10class System;
11}
12
9namespace Service::BCAT { 13namespace Service::BCAT {
10 14
11class BCAT final : public Module::Interface { 15class BCAT final : public Module::Interface {
12public: 16public:
13 explicit BCAT(std::shared_ptr<Module> module, const char* name); 17 explicit BCAT(Core::System& system, std::shared_ptr<Module> module,
18 FileSystem::FileSystemController& fsc, const char* name);
14 ~BCAT() override; 19 ~BCAT() override;
15}; 20};
16 21
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index b7bd738fc..4e4aa758b 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -2,34 +2,257 @@
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 <cctype>
6#include <mbedtls/md5.h>
7#include "backend/boxcat.h"
8#include "common/hex_util.h"
5#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/string_util.h"
11#include "core/file_sys/vfs.h"
6#include "core/hle/ipc_helpers.h" 12#include "core/hle/ipc_helpers.h"
13#include "core/hle/kernel/process.h"
14#include "core/hle/kernel/readable_event.h"
15#include "core/hle/kernel/writable_event.h"
16#include "core/hle/service/bcat/backend/backend.h"
7#include "core/hle/service/bcat/bcat.h" 17#include "core/hle/service/bcat/bcat.h"
8#include "core/hle/service/bcat/module.h" 18#include "core/hle/service/bcat/module.h"
19#include "core/hle/service/filesystem/filesystem.h"
20#include "core/settings.h"
9 21
10namespace Service::BCAT { 22namespace Service::BCAT {
11 23
24constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
25constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
26constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
27constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
28
29// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
30// and if any of them have a non-zero result it just forwards that result. This is the FS error code
31// for permission denied, which is the closest approximation of this scenario.
32constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
33
34using BCATDigest = std::array<u8, 0x10>;
35
36namespace {
37
38u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
39 u64 out{};
40 std::memcpy(&out, id.data(), sizeof(u64));
41 return out;
42}
43
44// The digest is only used to determine if a file is unique compared to others of the same name.
45// Since the algorithm isn't ever checked in game, MD5 is safe.
46BCATDigest DigestFile(const FileSys::VirtualFile& file) {
47 BCATDigest out{};
48 const auto bytes = file->ReadAllBytes();
49 mbedtls_md5(bytes.data(), bytes.size(), out.data());
50 return out;
51}
52
53// For a name to be valid it must be non-empty, must have a null terminating character as the final
54// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
55// file.
56bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
57 char match_char) {
58 const auto null_chars = std::count(name.begin(), name.end(), 0);
59 const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
60 return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
61 });
62 if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
63 LOG_ERROR(Service_BCAT, "Name passed was invalid!");
64 IPC::ResponseBuilder rb{ctx, 2};
65 rb.Push(ERROR_INVALID_ARGUMENT);
66 return false;
67 }
68
69 return true;
70}
71
72bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
73 return VerifyNameValidInternal(ctx, name, '-');
74}
75
76bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
77 return VerifyNameValidInternal(ctx, name, '.');
78}
79
80} // Anonymous namespace
81
82struct DeliveryCacheDirectoryEntry {
83 FileName name;
84 u64 size;
85 BCATDigest digest;
86};
87
88class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
89public:
90 IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
91 const DeliveryCacheProgressImpl& impl)
92 : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
93 // clang-format off
94 static const FunctionInfo functions[] = {
95 {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
96 {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
97 };
98 // clang-format on
99
100 RegisterHandlers(functions);
101 }
102
103private:
104 void GetEvent(Kernel::HLERequestContext& ctx) {
105 LOG_DEBUG(Service_BCAT, "called");
106
107 IPC::ResponseBuilder rb{ctx, 2, 1};
108 rb.Push(RESULT_SUCCESS);
109 rb.PushCopyObjects(event);
110 }
111
112 void GetImpl(Kernel::HLERequestContext& ctx) {
113 LOG_DEBUG(Service_BCAT, "called");
114
115 ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
116
117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(RESULT_SUCCESS);
119 }
120
121 Kernel::SharedPtr<Kernel::ReadableEvent> event;
122 const DeliveryCacheProgressImpl& impl;
123};
124
12class IBcatService final : public ServiceFramework<IBcatService> { 125class IBcatService final : public ServiceFramework<IBcatService> {
13public: 126public:
14 IBcatService() : ServiceFramework("IBcatService") { 127 explicit IBcatService(Core::System& system_, Backend& backend_)
128 : ServiceFramework("IBcatService"), system{system_}, backend{backend_} {
129 // clang-format off
15 static const FunctionInfo functions[] = { 130 static const FunctionInfo functions[] = {
16 {10100, nullptr, "RequestSyncDeliveryCache"}, 131 {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
17 {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"}, 132 {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
18 {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, 133 {10200, nullptr, "CancelSyncDeliveryCacheRequest"},
19 {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, 134 {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
20 {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, 135 {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
21 {30100, nullptr, "SetPassphrase"}, 136 {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
22 {30200, nullptr, "RegisterBackgroundDeliveryTask"}, 137 {30200, nullptr, "RegisterBackgroundDeliveryTask"},
23 {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, 138 {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
24 {30202, nullptr, "BlockDeliveryTask"}, 139 {30202, nullptr, "BlockDeliveryTask"},
25 {30203, nullptr, "UnblockDeliveryTask"}, 140 {30203, nullptr, "UnblockDeliveryTask"},
26 {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, 141 {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
27 {90200, nullptr, "GetDeliveryList"}, 142 {90200, nullptr, "GetDeliveryList"},
28 {90201, nullptr, "ClearDeliveryCacheStorage"}, 143 {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
29 {90300, nullptr, "GetPushNotificationLog"}, 144 {90300, nullptr, "GetPushNotificationLog"},
30 }; 145 };
146 // clang-format on
31 RegisterHandlers(functions); 147 RegisterHandlers(functions);
32 } 148 }
149
150private:
151 enum class SyncType {
152 Normal,
153 Directory,
154 Count,
155 };
156
157 std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
158 auto& backend{progress.at(static_cast<std::size_t>(type))};
159 return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
160 backend.GetImpl());
161 }
162
163 void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
164 LOG_DEBUG(Service_BCAT, "called");
165
166 backend.Synchronize({system.CurrentProcess()->GetTitleID(),
167 GetCurrentBuildID(system.GetCurrentProcessBuildID())},
168 progress.at(static_cast<std::size_t>(SyncType::Normal)));
169
170 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
171 rb.Push(RESULT_SUCCESS);
172 rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
173 }
174
175 void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
176 IPC::RequestParser rp{ctx};
177 const auto name_raw = rp.PopRaw<DirectoryName>();
178 const auto name =
179 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
180
181 LOG_DEBUG(Service_BCAT, "called, name={}", name);
182
183 backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(),
184 GetCurrentBuildID(system.GetCurrentProcessBuildID())},
185 name,
186 progress.at(static_cast<std::size_t>(SyncType::Directory)));
187
188 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
189 rb.Push(RESULT_SUCCESS);
190 rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
191 }
192
193 void SetPassphrase(Kernel::HLERequestContext& ctx) {
194 IPC::RequestParser rp{ctx};
195 const auto title_id = rp.PopRaw<u64>();
196
197 const auto passphrase_raw = ctx.ReadBuffer();
198
199 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
200 Common::HexToString(passphrase_raw));
201
202 if (title_id == 0) {
203 LOG_ERROR(Service_BCAT, "Invalid title ID!");
204 IPC::ResponseBuilder rb{ctx, 2};
205 rb.Push(ERROR_INVALID_ARGUMENT);
206 }
207
208 if (passphrase_raw.size() > 0x40) {
209 LOG_ERROR(Service_BCAT, "Passphrase too large!");
210 IPC::ResponseBuilder rb{ctx, 2};
211 rb.Push(ERROR_INVALID_ARGUMENT);
212 return;
213 }
214
215 Passphrase passphrase{};
216 std::memcpy(passphrase.data(), passphrase_raw.data(),
217 std::min(passphrase.size(), passphrase_raw.size()));
218
219 backend.SetPassphrase(title_id, passphrase);
220
221 IPC::ResponseBuilder rb{ctx, 2};
222 rb.Push(RESULT_SUCCESS);
223 }
224
225 void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
226 IPC::RequestParser rp{ctx};
227 const auto title_id = rp.PopRaw<u64>();
228
229 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
230
231 if (title_id == 0) {
232 LOG_ERROR(Service_BCAT, "Invalid title ID!");
233 IPC::ResponseBuilder rb{ctx, 2};
234 rb.Push(ERROR_INVALID_ARGUMENT);
235 return;
236 }
237
238 if (!backend.Clear(title_id)) {
239 LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
240 IPC::ResponseBuilder rb{ctx, 2};
241 rb.Push(ERROR_FAILED_CLEAR_CACHE);
242 return;
243 }
244
245 IPC::ResponseBuilder rb{ctx, 2};
246 rb.Push(RESULT_SUCCESS);
247 }
248
249 Core::System& system;
250 Backend& backend;
251
252 std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
253 ProgressServiceBackend{"Normal"},
254 ProgressServiceBackend{"Directory"},
255 };
33}; 256};
34 257
35void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { 258void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
@@ -37,20 +260,332 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
37 260
38 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 261 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
39 rb.Push(RESULT_SUCCESS); 262 rb.Push(RESULT_SUCCESS);
40 rb.PushIpcInterface<IBcatService>(); 263 rb.PushIpcInterface<IBcatService>(system, *backend);
264}
265
266class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
267public:
268 IDeliveryCacheFileService(FileSys::VirtualDir root_)
269 : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
270 // clang-format off
271 static const FunctionInfo functions[] = {
272 {0, &IDeliveryCacheFileService::Open, "Open"},
273 {1, &IDeliveryCacheFileService::Read, "Read"},
274 {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
275 {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
276 };
277 // clang-format on
278
279 RegisterHandlers(functions);
280 }
281
282private:
283 void Open(Kernel::HLERequestContext& ctx) {
284 IPC::RequestParser rp{ctx};
285 const auto dir_name_raw = rp.PopRaw<DirectoryName>();
286 const auto file_name_raw = rp.PopRaw<FileName>();
287
288 const auto dir_name =
289 Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
290 const auto file_name =
291 Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
292
293 LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
294
295 if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
296 return;
297
298 if (current_file != nullptr) {
299 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
300 IPC::ResponseBuilder rb{ctx, 2};
301 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
302 return;
303 }
304
305 const auto dir = root->GetSubdirectory(dir_name);
306
307 if (dir == nullptr) {
308 LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
309 IPC::ResponseBuilder rb{ctx, 2};
310 rb.Push(ERROR_FAILED_OPEN_ENTITY);
311 return;
312 }
313
314 current_file = dir->GetFile(file_name);
315
316 if (current_file == nullptr) {
317 LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
318 IPC::ResponseBuilder rb{ctx, 2};
319 rb.Push(ERROR_FAILED_OPEN_ENTITY);
320 return;
321 }
322
323 IPC::ResponseBuilder rb{ctx, 2};
324 rb.Push(RESULT_SUCCESS);
325 }
326
327 void Read(Kernel::HLERequestContext& ctx) {
328 IPC::RequestParser rp{ctx};
329 const auto offset{rp.PopRaw<u64>()};
330
331 auto size = ctx.GetWriteBufferSize();
332
333 LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
334
335 if (current_file == nullptr) {
336 LOG_ERROR(Service_BCAT, "There is no file currently open!");
337 IPC::ResponseBuilder rb{ctx, 2};
338 rb.Push(ERROR_NO_OPEN_ENTITY);
339 }
340
341 size = std::min<u64>(current_file->GetSize() - offset, size);
342 const auto buffer = current_file->ReadBytes(size, offset);
343 ctx.WriteBuffer(buffer);
344
345 IPC::ResponseBuilder rb{ctx, 4};
346 rb.Push(RESULT_SUCCESS);
347 rb.Push<u64>(buffer.size());
348 }
349
350 void GetSize(Kernel::HLERequestContext& ctx) {
351 LOG_DEBUG(Service_BCAT, "called");
352
353 if (current_file == nullptr) {
354 LOG_ERROR(Service_BCAT, "There is no file currently open!");
355 IPC::ResponseBuilder rb{ctx, 2};
356 rb.Push(ERROR_NO_OPEN_ENTITY);
357 }
358
359 IPC::ResponseBuilder rb{ctx, 4};
360 rb.Push(RESULT_SUCCESS);
361 rb.Push<u64>(current_file->GetSize());
362 }
363
364 void GetDigest(Kernel::HLERequestContext& ctx) {
365 LOG_DEBUG(Service_BCAT, "called");
366
367 if (current_file == nullptr) {
368 LOG_ERROR(Service_BCAT, "There is no file currently open!");
369 IPC::ResponseBuilder rb{ctx, 2};
370 rb.Push(ERROR_NO_OPEN_ENTITY);
371 }
372
373 IPC::ResponseBuilder rb{ctx, 6};
374 rb.Push(RESULT_SUCCESS);
375 rb.PushRaw(DigestFile(current_file));
376 }
377
378 FileSys::VirtualDir root;
379 FileSys::VirtualFile current_file;
380};
381
382class IDeliveryCacheDirectoryService final
383 : public ServiceFramework<IDeliveryCacheDirectoryService> {
384public:
385 IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
386 : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
387 // clang-format off
388 static const FunctionInfo functions[] = {
389 {0, &IDeliveryCacheDirectoryService::Open, "Open"},
390 {1, &IDeliveryCacheDirectoryService::Read, "Read"},
391 {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
392 };
393 // clang-format on
394
395 RegisterHandlers(functions);
396 }
397
398private:
399 void Open(Kernel::HLERequestContext& ctx) {
400 IPC::RequestParser rp{ctx};
401 const auto name_raw = rp.PopRaw<DirectoryName>();
402 const auto name =
403 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
404
405 LOG_DEBUG(Service_BCAT, "called, name={}", name);
406
407 if (!VerifyNameValidDir(ctx, name_raw))
408 return;
409
410 if (current_dir != nullptr) {
411 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
412 IPC::ResponseBuilder rb{ctx, 2};
413 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
414 return;
415 }
416
417 current_dir = root->GetSubdirectory(name);
418
419 if (current_dir == nullptr) {
420 LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
421 IPC::ResponseBuilder rb{ctx, 2};
422 rb.Push(ERROR_FAILED_OPEN_ENTITY);
423 return;
424 }
425
426 IPC::ResponseBuilder rb{ctx, 2};
427 rb.Push(RESULT_SUCCESS);
428 }
429
430 void Read(Kernel::HLERequestContext& ctx) {
431 auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
432
433 LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
434
435 if (current_dir == nullptr) {
436 LOG_ERROR(Service_BCAT, "There is no open directory!");
437 IPC::ResponseBuilder rb{ctx, 2};
438 rb.Push(ERROR_NO_OPEN_ENTITY);
439 return;
440 }
441
442 const auto files = current_dir->GetFiles();
443 write_size = std::min<u64>(write_size, files.size());
444 std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
445 std::transform(
446 files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
447 FileName name{};
448 std::memcpy(name.data(), file->GetName().data(),
449 std::min(file->GetName().size(), name.size()));
450 return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
451 });
452
453 ctx.WriteBuffer(entries);
454
455 IPC::ResponseBuilder rb{ctx, 3};
456 rb.Push(RESULT_SUCCESS);
457 rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)));
458 }
459
460 void GetCount(Kernel::HLERequestContext& ctx) {
461 LOG_DEBUG(Service_BCAT, "called");
462
463 if (current_dir == nullptr) {
464 LOG_ERROR(Service_BCAT, "There is no open directory!");
465 IPC::ResponseBuilder rb{ctx, 2};
466 rb.Push(ERROR_NO_OPEN_ENTITY);
467 return;
468 }
469
470 const auto files = current_dir->GetFiles();
471
472 IPC::ResponseBuilder rb{ctx, 3};
473 rb.Push(RESULT_SUCCESS);
474 rb.Push(static_cast<u32>(files.size()));
475 }
476
477 FileSys::VirtualDir root;
478 FileSys::VirtualDir current_dir;
479};
480
481class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
482public:
483 IDeliveryCacheStorageService(FileSys::VirtualDir root_)
484 : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
485 // clang-format off
486 static const FunctionInfo functions[] = {
487 {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
488 {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
489 {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
490 };
491 // clang-format on
492
493 RegisterHandlers(functions);
494
495 for (const auto& subdir : root->GetSubdirectories()) {
496 DirectoryName name{};
497 std::memcpy(name.data(), subdir->GetName().data(),
498 std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
499 entries.push_back(name);
500 }
501 }
502
503private:
504 void CreateFileService(Kernel::HLERequestContext& ctx) {
505 LOG_DEBUG(Service_BCAT, "called");
506
507 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
508 rb.Push(RESULT_SUCCESS);
509 rb.PushIpcInterface<IDeliveryCacheFileService>(root);
510 }
511
512 void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
513 LOG_DEBUG(Service_BCAT, "called");
514
515 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
516 rb.Push(RESULT_SUCCESS);
517 rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
518 }
519
520 void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
521 auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
522
523 LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
524
525 size = std::min<u64>(size, entries.size() - next_read_index);
526 ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
527 next_read_index += size;
528
529 IPC::ResponseBuilder rb{ctx, 3};
530 rb.Push(RESULT_SUCCESS);
531 rb.Push(static_cast<u32>(size));
532 }
533
534 FileSys::VirtualDir root;
535 std::vector<DirectoryName> entries;
536 u64 next_read_index = 0;
537};
538
539void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
540 LOG_DEBUG(Service_BCAT, "called");
541
542 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
543 rb.Push(RESULT_SUCCESS);
544 rb.PushIpcInterface<IDeliveryCacheStorageService>(
545 fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID()));
546}
547
548void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
549 Kernel::HLERequestContext& ctx) {
550 IPC::RequestParser rp{ctx};
551 const auto title_id = rp.PopRaw<u64>();
552
553 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
554
555 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
556 rb.Push(RESULT_SUCCESS);
557 rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
558}
559
560std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
561 const auto backend = Settings::values.bcat_backend;
562
563#ifdef YUZU_ENABLE_BOXCAT
564 if (backend == "boxcat")
565 return std::make_unique<Boxcat>(std::move(getter));
566#endif
567
568 return std::make_unique<NullBackend>(std::move(getter));
41} 569}
42 570
43Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) 571Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
44 : ServiceFramework(name), module(std::move(module)) {} 572 FileSystem::FileSystemController& fsc_, const char* name)
573 : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)},
574 backend{CreateBackendFromSettings([&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })},
575 system{system_} {}
45 576
46Module::Interface::~Interface() = default; 577Module::Interface::~Interface() = default;
47 578
48void InstallInterfaces(SM::ServiceManager& service_manager) { 579void InstallInterfaces(Core::System& system) {
49 auto module = std::make_shared<Module>(); 580 auto module = std::make_shared<Module>();
50 std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); 581 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a")
51 std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager); 582 ->InstallAsService(system.ServiceManager());
52 std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager); 583 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m")
53 std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager); 584 ->InstallAsService(system.ServiceManager());
585 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u")
586 ->InstallAsService(system.ServiceManager());
587 std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s")
588 ->InstallAsService(system.ServiceManager());
54} 589}
55 590
56} // namespace Service::BCAT 591} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index f0d63cab0..e4ba23ba0 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -6,23 +6,46 @@
6 6
7#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
8 8
9namespace Service::BCAT { 9namespace Core {
10class System;
11}
12
13namespace Service {
14
15namespace FileSystem {
16class FileSystemController;
17} // namespace FileSystem
18
19namespace BCAT {
20
21class Backend;
10 22
11class Module final { 23class Module final {
12public: 24public:
13 class Interface : public ServiceFramework<Interface> { 25 class Interface : public ServiceFramework<Interface> {
14 public: 26 public:
15 explicit Interface(std::shared_ptr<Module> module, const char* name); 27 explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
28 FileSystem::FileSystemController& fsc_, const char* name);
16 ~Interface() override; 29 ~Interface() override;
17 30
18 void CreateBcatService(Kernel::HLERequestContext& ctx); 31 void CreateBcatService(Kernel::HLERequestContext& ctx);
32 void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
33 void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
19 34
20 protected: 35 protected:
36 FileSystem::FileSystemController& fsc;
37
21 std::shared_ptr<Module> module; 38 std::shared_ptr<Module> module;
39 std::unique_ptr<Backend> backend;
40
41 private:
42 Core::System& system;
22 }; 43 };
23}; 44};
24 45
25/// Registers all BCAT services with the specified service manager. 46/// Registers all BCAT services with the specified service manager.
26void InstallInterfaces(SM::ServiceManager& service_manager); 47void InstallInterfaces(Core::System& system);
48
49} // namespace BCAT
27 50
28} // namespace Service::BCAT 51} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index b2ebf6240..2546d7595 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -66,7 +66,7 @@ enum class FatalType : u32 {
66 66
67static void GenerateErrorReport(Core::System& system, ResultCode error_code, 67static void GenerateErrorReport(Core::System& system, ResultCode error_code,
68 const FatalInfo& info) { 68 const FatalInfo& info) {
69 const auto title_id = Core::CurrentProcess()->GetTitleID(); 69 const auto title_id = system.CurrentProcess()->GetTitleID();
70 std::string crash_report = fmt::format( 70 std::string crash_report = fmt::format(
71 "Yuzu {}-{} crash report\n" 71 "Yuzu {}-{} crash report\n"
72 "Title ID: {:016x}\n" 72 "Title ID: {:016x}\n"
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 14cd0e322..11e5c56b7 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -241,7 +241,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
241 return FileSys::ERROR_PATH_NOT_FOUND; 241 return FileSys::ERROR_PATH_NOT_FOUND;
242} 242}
243 243
244FileSystemController::FileSystemController() = default; 244FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}
245 245
246FileSystemController::~FileSystemController() = default; 246FileSystemController::~FileSystemController() = default;
247 247
@@ -290,7 +290,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()
290 return ResultCode(-1); 290 return ResultCode(-1);
291 } 291 }
292 292
293 return romfs_factory->OpenCurrentProcess(); 293 return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());
294} 294}
295 295
296ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( 296ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
@@ -447,10 +447,10 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy
447 FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE}; 447 FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE};
448 448
449 FileSys::NACP nacp; 449 FileSys::NACP nacp;
450 const auto res = Core::System::GetInstance().GetAppLoader().ReadControlData(nacp); 450 const auto res = system.GetAppLoader().ReadControlData(nacp);
451 451
452 if (res != Loader::ResultStatus::Success) { 452 if (res != Loader::ResultStatus::Success) {
453 FileSys::PatchManager pm{Core::CurrentProcess()->GetTitleID()}; 453 FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
454 auto [nacp_unique, discard] = pm.GetControlMetadata(); 454 auto [nacp_unique, discard] = pm.GetControlMetadata();
455 455
456 if (nacp_unique != nullptr) { 456 if (nacp_unique != nullptr) {
@@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
674 return bis_factory->GetModificationDumpRoot(title_id); 674 return bis_factory->GetModificationDumpRoot(title_id);
675} 675}
676 676
677FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
678 LOG_TRACE(Service_FS, "Opening BCAT root for tid={:016X}", title_id);
679
680 if (bis_factory == nullptr)
681 return nullptr;
682
683 return bis_factory->GetBCATDirectory(title_id);
684}
685
677void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { 686void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
678 if (overwrite) { 687 if (overwrite) {
679 bis_factory = nullptr; 688 bis_factory = nullptr;
@@ -693,10 +702,10 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
693 if (bis_factory == nullptr) { 702 if (bis_factory == nullptr) {
694 bis_factory = 703 bis_factory =
695 std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); 704 std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
696 Core::System::GetInstance().RegisterContentProvider( 705 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
697 FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents()); 706 bis_factory->GetSystemNANDContents());
698 Core::System::GetInstance().RegisterContentProvider( 707 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
699 FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents()); 708 bis_factory->GetUserNANDContents());
700 } 709 }
701 710
702 if (save_data_factory == nullptr) { 711 if (save_data_factory == nullptr) {
@@ -705,8 +714,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
705 714
706 if (sdmc_factory == nullptr) { 715 if (sdmc_factory == nullptr) {
707 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); 716 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
708 Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, 717 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
709 sdmc_factory->GetSDMCContents()); 718 sdmc_factory->GetSDMCContents());
710 } 719 }
711} 720}
712 721
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3e0c03ec0..1b0a6a949 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -10,6 +10,10 @@
10#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
11#include "core/hle/result.h" 11#include "core/hle/result.h"
12 12
13namespace Core {
14class System;
15}
16
13namespace FileSys { 17namespace FileSys {
14class BISFactory; 18class BISFactory;
15class RegisteredCache; 19class RegisteredCache;
@@ -52,7 +56,7 @@ enum class ImageDirectoryId : u32 {
52 56
53class FileSystemController { 57class FileSystemController {
54public: 58public:
55 FileSystemController(); 59 explicit FileSystemController(Core::System& system_);
56 ~FileSystemController(); 60 ~FileSystemController();
57 61
58 ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); 62 ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
@@ -110,6 +114,8 @@ public:
110 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; 114 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
111 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; 115 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
112 116
117 FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
118
113 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function 119 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
114 // above is called. 120 // above is called.
115 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); 121 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
@@ -123,6 +129,8 @@ private:
123 std::unique_ptr<FileSys::XCI> gamecard; 129 std::unique_ptr<FileSys::XCI> gamecard;
124 std::unique_ptr<FileSys::RegisteredCache> gamecard_registered; 130 std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
125 std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder; 131 std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
132
133 Core::System& system;
126}; 134};
127 135
128void InstallInterfaces(Core::System& system); 136void InstallInterfaces(Core::System& system);
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index eb982ad49..cbd5466c1 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -803,7 +803,7 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
803 IPC::RequestParser rp{ctx}; 803 IPC::RequestParser rp{ctx};
804 804
805 auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); 805 auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
806 auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); 806 [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
807 u128 uid = rp.PopRaw<u128>(); 807 u128 uid = rp.PopRaw<u128>();
808 808
809 LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), 809 LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 42b4ee861..75dd9043b 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -237,7 +237,6 @@ private:
237 }; 237 };
238 238
239 Common::UUID uuid; 239 Common::UUID uuid;
240 bool is_event_created = false;
241 Kernel::EventPair notification_event; 240 Kernel::EventPair notification_event;
242 std::queue<SizedNotificationInfo> notifications; 241 std::queue<SizedNotificationInfo> notifications;
243 States states{}; 242 States states{};
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index 8e8263f5b..1f2131ec8 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -11,11 +11,10 @@
11namespace Service::HID { 11namespace Service::HID {
12 12
13constexpr s32 HID_JOYSTICK_MAX = 0x7fff; 13constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
14constexpr s32 HID_JOYSTICK_MIN = -0x7fff; 14[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
15enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; 15enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
16 16
17Controller_DebugPad::Controller_DebugPad(Core::System& system) 17Controller_DebugPad::Controller_DebugPad(Core::System& system) : ControllerBase(system) {}
18 : ControllerBase(system), system(system) {}
19Controller_DebugPad::~Controller_DebugPad() = default; 18Controller_DebugPad::~Controller_DebugPad() = default;
20 19
21void Controller_DebugPad::OnInit() {} 20void Controller_DebugPad::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index 6c4de817e..555b29d76 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -89,6 +89,5 @@ private:
89 buttons; 89 buttons;
90 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> 90 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>
91 analogs; 91 analogs;
92 Core::System& system;
93}; 92};
94} // namespace Service::HID 93} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 80da0a0d3..6e990dd00 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -10,8 +10,7 @@
10namespace Service::HID { 10namespace Service::HID {
11constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00; 11constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
12 12
13Controller_Gesture::Controller_Gesture(Core::System& system) 13Controller_Gesture::Controller_Gesture(Core::System& system) : ControllerBase(system) {}
14 : ControllerBase(system), system(system) {}
15Controller_Gesture::~Controller_Gesture() = default; 14Controller_Gesture::~Controller_Gesture() = default;
16 15
17void Controller_Gesture::OnInit() {} 16void Controller_Gesture::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
index 396897527..f650b8338 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -59,6 +59,5 @@ private:
59 std::array<GestureState, 17> gesture_states; 59 std::array<GestureState, 17> gesture_states;
60 }; 60 };
61 SharedMemory shared_memory{}; 61 SharedMemory shared_memory{};
62 Core::System& system;
63}; 62};
64} // namespace Service::HID 63} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index e587b2e15..358cb9329 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -12,8 +12,7 @@ namespace Service::HID {
12constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; 12constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
13constexpr u8 KEYS_PER_BYTE = 8; 13constexpr u8 KEYS_PER_BYTE = 8;
14 14
15Controller_Keyboard::Controller_Keyboard(Core::System& system) 15Controller_Keyboard::Controller_Keyboard(Core::System& system) : ControllerBase(system) {}
16 : ControllerBase(system), system(system) {}
17Controller_Keyboard::~Controller_Keyboard() = default; 16Controller_Keyboard::~Controller_Keyboard() = default;
18 17
19void Controller_Keyboard::OnInit() {} 18void Controller_Keyboard::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
index ef586f7eb..f3eef5936 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -53,6 +53,5 @@ private:
53 keyboard_keys; 53 keyboard_keys;
54 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods> 54 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
55 keyboard_mods; 55 keyboard_mods;
56 Core::System& system;
57}; 56};
58} // namespace Service::HID 57} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index 88f2ca4c1..93d88ea50 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -11,7 +11,7 @@
11namespace Service::HID { 11namespace Service::HID {
12constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; 12constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
13 13
14Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(system) {} 14Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system) {}
15Controller_Mouse::~Controller_Mouse() = default; 15Controller_Mouse::~Controller_Mouse() = default;
16 16
17void Controller_Mouse::OnInit() {} 17void Controller_Mouse::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
index df2da6ae3..357ab7107 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -53,6 +53,5 @@ private:
53 std::unique_ptr<Input::MouseDevice> mouse_device; 53 std::unique_ptr<Input::MouseDevice> mouse_device;
54 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons> 54 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons>
55 mouse_button_devices; 55 mouse_button_devices;
56 Core::System& system;
57}; 56};
58} // namespace Service::HID 57} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 44b668fbf..a2b25a796 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -20,7 +20,7 @@
20 20
21namespace Service::HID { 21namespace Service::HID {
22constexpr s32 HID_JOYSTICK_MAX = 0x7fff; 22constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
23constexpr s32 HID_JOYSTICK_MIN = -0x7fff; 23[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
24constexpr std::size_t NPAD_OFFSET = 0x9A00; 24constexpr std::size_t NPAD_OFFSET = 0x9A00;
25constexpr u32 BATTERY_FULL = 2; 25constexpr u32 BATTERY_FULL = 2;
26constexpr u32 MAX_NPAD_ID = 7; 26constexpr u32 MAX_NPAD_ID = 7;
@@ -105,6 +105,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
105 controller.joy_styles.raw = 0; // Zero out 105 controller.joy_styles.raw = 0; // Zero out
106 controller.device_type.raw = 0; 106 controller.device_type.raw = 0;
107 switch (controller_type) { 107 switch (controller_type) {
108 case NPadControllerType::None:
109 UNREACHABLE();
108 case NPadControllerType::Handheld: 110 case NPadControllerType::Handheld:
109 controller.joy_styles.handheld.Assign(1); 111 controller.joy_styles.handheld.Assign(1);
110 controller.device_type.handheld.Assign(1); 112 controller.device_type.handheld.Assign(1);
@@ -165,13 +167,14 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
165 controller.battery_level[0] = BATTERY_FULL; 167 controller.battery_level[0] = BATTERY_FULL;
166 controller.battery_level[1] = BATTERY_FULL; 168 controller.battery_level[1] = BATTERY_FULL;
167 controller.battery_level[2] = BATTERY_FULL; 169 controller.battery_level[2] = BATTERY_FULL;
170 styleset_changed_events[controller_idx].writable->Signal();
168} 171}
169 172
170void Controller_NPad::OnInit() { 173void Controller_NPad::OnInit() {
171 auto& kernel = system.Kernel(); 174 auto& kernel = system.Kernel();
172 for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { 175 for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
173 styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( 176 styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
174 kernel, Kernel::ResetType::Automatic, fmt::format("npad:NpadStyleSetChanged_{}", i)); 177 kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i));
175 } 178 }
176 179
177 if (!IsControllerActivated()) { 180 if (!IsControllerActivated()) {
@@ -238,7 +241,7 @@ void Controller_NPad::OnRelease() {}
238 241
239void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { 242void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
240 const auto controller_idx = NPadIdToIndex(npad_id); 243 const auto controller_idx = NPadIdToIndex(npad_id);
241 const auto controller_type = connected_controllers[controller_idx].type; 244 [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type;
242 if (!connected_controllers[controller_idx].is_connected) { 245 if (!connected_controllers[controller_idx].is_connected) {
243 return; 246 return;
244 } 247 }
@@ -345,6 +348,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
345 libnx_entry.connection_status.raw = 0; 348 libnx_entry.connection_status.raw = 0;
346 349
347 switch (controller_type) { 350 switch (controller_type) {
351 case NPadControllerType::None:
352 UNREACHABLE();
348 case NPadControllerType::Handheld: 353 case NPadControllerType::Handheld:
349 handheld_entry.connection_status.raw = 0; 354 handheld_entry.connection_status.raw = 0;
350 handheld_entry.connection_status.IsWired.Assign(1); 355 handheld_entry.connection_status.IsWired.Assign(1);
@@ -433,7 +438,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
433 supported_npad_id_types.clear(); 438 supported_npad_id_types.clear();
434 supported_npad_id_types.resize(length / sizeof(u32)); 439 supported_npad_id_types.resize(length / sizeof(u32));
435 std::memcpy(supported_npad_id_types.data(), data, length); 440 std::memcpy(supported_npad_id_types.data(), data, length);
436 bool had_controller_update = false;
437 for (std::size_t i = 0; i < connected_controllers.size(); i++) { 441 for (std::size_t i = 0; i < connected_controllers.size(); i++) {
438 auto& controller = connected_controllers[i]; 442 auto& controller = connected_controllers[i];
439 if (!controller.is_connected) { 443 if (!controller.is_connected) {
@@ -452,10 +456,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
452 controller.type = requested_controller; 456 controller.type = requested_controller;
453 InitNewlyAddedControler(i); 457 InitNewlyAddedControler(i);
454 } 458 }
455 had_controller_update = true;
456 }
457 if (had_controller_update) {
458 styleset_changed_events[i].writable->Signal();
459 } 459 }
460 } 460 }
461} 461}
@@ -481,7 +481,6 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
481 const std::size_t npad_index = NPadIdToIndex(npad_id); 481 const std::size_t npad_index = NPadIdToIndex(npad_id);
482 ASSERT(npad_index < shared_memory_entries.size()); 482 ASSERT(npad_index < shared_memory_entries.size());
483 if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { 483 if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
484 styleset_changed_events[npad_index].writable->Signal();
485 shared_memory_entries[npad_index].pad_assignment = assignment_mode; 484 shared_memory_entries[npad_index].pad_assignment = assignment_mode;
486 } 485 }
487} 486}
@@ -507,7 +506,6 @@ Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEven
507 // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should 506 // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
508 // be signalled at least once, and signaled after a new controller is connected? 507 // be signalled at least once, and signaled after a new controller is connected?
509 const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; 508 const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
510 styleset_event.writable->Signal();
511 return styleset_event.readable; 509 return styleset_event.readable;
512} 510}
513 511
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
index 9b829341e..9e527d176 100644
--- a/src/core/hle/service/hid/controllers/stubbed.cpp
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -9,8 +9,7 @@
9 9
10namespace Service::HID { 10namespace Service::HID {
11 11
12Controller_Stubbed::Controller_Stubbed(Core::System& system) 12Controller_Stubbed::Controller_Stubbed(Core::System& system) : ControllerBase(system) {}
13 : ControllerBase(system), system(system) {}
14Controller_Stubbed::~Controller_Stubbed() = default; 13Controller_Stubbed::~Controller_Stubbed() = default;
15 14
16void Controller_Stubbed::OnInit() {} 15void Controller_Stubbed::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h
index 37d7d8538..4fa83ac85 100644
--- a/src/core/hle/service/hid/controllers/stubbed.h
+++ b/src/core/hle/service/hid/controllers/stubbed.h
@@ -30,6 +30,5 @@ public:
30private: 30private:
31 bool smart_update{}; 31 bool smart_update{};
32 std::size_t common_offset{}; 32 std::size_t common_offset{};
33 Core::System& system;
34}; 33};
35} // namespace Service::HID 34} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 25912fd69..1c6e55566 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -13,8 +13,7 @@
13namespace Service::HID { 13namespace Service::HID {
14constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; 14constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
15 15
16Controller_Touchscreen::Controller_Touchscreen(Core::System& system) 16Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {}
17 : ControllerBase(system), system(system) {}
18Controller_Touchscreen::~Controller_Touchscreen() = default; 17Controller_Touchscreen::~Controller_Touchscreen() = default;
19 18
20void Controller_Touchscreen::OnInit() {} 19void Controller_Touchscreen::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index 3429c84db..a1d97269e 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -69,6 +69,5 @@ private:
69 TouchScreenSharedMemory shared_memory{}; 69 TouchScreenSharedMemory shared_memory{};
70 std::unique_ptr<Input::TouchDevice> touch_device; 70 std::unique_ptr<Input::TouchDevice> touch_device;
71 s64_le last_touch{}; 71 s64_le last_touch{};
72 Core::System& system;
73}; 72};
74} // namespace Service::HID 73} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
index 1bce044b4..27511b27b 100644
--- a/src/core/hle/service/hid/controllers/xpad.cpp
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -10,7 +10,7 @@
10namespace Service::HID { 10namespace Service::HID {
11constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; 11constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
12 12
13Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(system) {} 13Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system) {}
14Controller_XPad::~Controller_XPad() = default; 14Controller_XPad::~Controller_XPad() = default;
15 15
16void Controller_XPad::OnInit() {} 16void Controller_XPad::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
index c445ebec0..ad229787c 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -56,6 +56,5 @@ private:
56 }; 56 };
57 static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size"); 57 static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
58 SharedMemory shared_memory{}; 58 SharedMemory shared_memory{};
59 Core::System& system;
60}; 59};
61} // namespace Service::HID 60} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 8d76ba746..ba1da4181 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -38,8 +38,10 @@ namespace Service::HID {
38// Updating period for each HID device. 38// Updating period for each HID device.
39// TODO(ogniK): Find actual polling rate of hid 39// TODO(ogniK): Find actual polling rate of hid
40constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66); 40constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66);
41constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); 41[[maybe_unused]] constexpr s64 accelerometer_update_ticks =
42constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); 42 static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
43[[maybe_unused]] constexpr s64 gyroscope_update_ticks =
44 static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
43constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; 45constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
44 46
45IAppletResource::IAppletResource(Core::System& system) 47IAppletResource::IAppletResource(Core::System& system)
@@ -193,7 +195,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
193 {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, 195 {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
194 {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, 196 {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
195 {103, &Hid::ActivateNpad, "ActivateNpad"}, 197 {103, &Hid::ActivateNpad, "ActivateNpad"},
196 {104, nullptr, "DeactivateNpad"}, 198 {104, &Hid::DeactivateNpad, "DeactivateNpad"},
197 {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, 199 {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
198 {107, &Hid::DisconnectNpad, "DisconnectNpad"}, 200 {107, &Hid::DisconnectNpad, "DisconnectNpad"},
199 {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, 201 {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
@@ -468,6 +470,17 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {
468 applet_resource->ActivateController(HidController::NPad); 470 applet_resource->ActivateController(HidController::NPad);
469} 471}
470 472
473void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) {
474 IPC::RequestParser rp{ctx};
475 const auto applet_resource_user_id{rp.Pop<u64>()};
476
477 LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
478
479 IPC::ResponseBuilder rb{ctx, 2};
480 rb.Push(RESULT_SUCCESS);
481 applet_resource->DeactivateController(HidController::NPad);
482}
483
471void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { 484void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
472 IPC::RequestParser rp{ctx}; 485 IPC::RequestParser rp{ctx};
473 const auto npad_id{rp.Pop<u32>()}; 486 const auto npad_id{rp.Pop<u32>()};
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 35b663679..01852e019 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -99,6 +99,7 @@ private:
99 void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); 99 void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
100 void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx); 100 void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
101 void ActivateNpad(Kernel::HLERequestContext& ctx); 101 void ActivateNpad(Kernel::HLERequestContext& ctx);
102 void DeactivateNpad(Kernel::HLERequestContext& ctx);
102 void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx); 103 void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
103 void DisconnectNpad(Kernel::HLERequestContext& ctx); 104 void DisconnectNpad(Kernel::HLERequestContext& ctx);
104 void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); 105 void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 3164ca26e..499376bfc 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -163,7 +163,7 @@ public:
163 return; 163 return;
164 } 164 }
165 165
166 if (Core::CurrentProcess()->GetTitleID() != header.title_id) { 166 if (system.CurrentProcess()->GetTitleID() != header.title_id) {
167 LOG_ERROR(Service_LDR, 167 LOG_ERROR(Service_LDR,
168 "Attempting to load NRR with title ID other than current process. (actual " 168 "Attempting to load NRR with title ID other than current process. (actual "
169 "{:016X})!", 169 "{:016X})!",
@@ -327,7 +327,7 @@ public:
327 } 327 }
328 328
329 // Load NRO as new executable module 329 // Load NRO as new executable module
330 auto* process = Core::CurrentProcess(); 330 auto* process = system.CurrentProcess();
331 auto& vm_manager = process->VMManager(); 331 auto& vm_manager = process->VMManager();
332 auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); 332 auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size);
333 333
@@ -411,7 +411,7 @@ public:
411 return; 411 return;
412 } 412 }
413 413
414 auto& vm_manager = Core::CurrentProcess()->VMManager(); 414 auto& vm_manager = system.CurrentProcess()->VMManager();
415 const auto& nro_info = iter->second; 415 const auto& nro_info = iter->second;
416 416
417 // Unmap the mirrored memory 417 // Unmap the mirrored memory
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index a42c22d44..aa886cd3e 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -18,8 +18,8 @@
18namespace Service::NFP { 18namespace Service::NFP {
19 19
20namespace ErrCodes { 20namespace ErrCodes {
21constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, 21[[maybe_unused]] constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
22 -1); // TODO(ogniK): Find the actual error code 22 -1); // TODO(ogniK): Find the actual error code
23constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152); 23constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
24} // namespace ErrCodes 24} // namespace ErrCodes
25 25
@@ -35,7 +35,7 @@ Module::Interface::~Interface() = default;
35class IUser final : public ServiceFramework<IUser> { 35class IUser final : public ServiceFramework<IUser> {
36public: 36public:
37 IUser(Module::Interface& nfp_interface, Core::System& system) 37 IUser(Module::Interface& nfp_interface, Core::System& system)
38 : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) { 38 : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
39 static const FunctionInfo functions[] = { 39 static const FunctionInfo functions[] = {
40 {0, &IUser::Initialize, "Initialize"}, 40 {0, &IUser::Initialize, "Initialize"},
41 {1, &IUser::Finalize, "Finalize"}, 41 {1, &IUser::Finalize, "Finalize"},
@@ -183,6 +183,8 @@ private:
183 case DeviceState::TagRemoved: 183 case DeviceState::TagRemoved:
184 device_state = DeviceState::Initialized; 184 device_state = DeviceState::Initialized;
185 break; 185 break;
186 default:
187 break;
186 } 188 }
187 IPC::ResponseBuilder rb{ctx, 2}; 189 IPC::ResponseBuilder rb{ctx, 2};
188 rb.Push(RESULT_SUCCESS); 190 rb.Push(RESULT_SUCCESS);
@@ -324,7 +326,6 @@ private:
324 Kernel::EventPair deactivate_event; 326 Kernel::EventPair deactivate_event;
325 Kernel::EventPair availability_change_event; 327 Kernel::EventPair availability_change_event;
326 const Module::Interface& nfp_interface; 328 const Module::Interface& nfp_interface;
327 Core::System& system;
328}; 329};
329 330
330void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { 331void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 24d1813a7..756a2af57 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -12,6 +12,13 @@
12 12
13namespace Service::NIFM { 13namespace Service::NIFM {
14 14
15enum class RequestState : u32 {
16 NotSubmitted = 1,
17 Error = 1, ///< The duplicate 1 is intentional; it means both not submitted and error on HW.
18 Pending = 2,
19 Connected = 3,
20};
21
15class IScanRequest final : public ServiceFramework<IScanRequest> { 22class IScanRequest final : public ServiceFramework<IScanRequest> {
16public: 23public:
17 explicit IScanRequest() : ServiceFramework("IScanRequest") { 24 explicit IScanRequest() : ServiceFramework("IScanRequest") {
@@ -81,7 +88,7 @@ private:
81 88
82 IPC::ResponseBuilder rb{ctx, 3}; 89 IPC::ResponseBuilder rb{ctx, 3};
83 rb.Push(RESULT_SUCCESS); 90 rb.Push(RESULT_SUCCESS);
84 rb.Push<u32>(0); 91 rb.PushEnum(RequestState::Connected);
85 } 92 }
86 93
87 void GetResult(Kernel::HLERequestContext& ctx) { 94 void GetResult(Kernel::HLERequestContext& ctx) {
@@ -189,14 +196,14 @@ private:
189 196
190 IPC::ResponseBuilder rb{ctx, 3}; 197 IPC::ResponseBuilder rb{ctx, 3};
191 rb.Push(RESULT_SUCCESS); 198 rb.Push(RESULT_SUCCESS);
192 rb.Push<u8>(0); 199 rb.Push<u8>(1);
193 } 200 }
194 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { 201 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
195 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 202 LOG_WARNING(Service_NIFM, "(STUBBED) called");
196 203
197 IPC::ResponseBuilder rb{ctx, 3}; 204 IPC::ResponseBuilder rb{ctx, 3};
198 rb.Push(RESULT_SUCCESS); 205 rb.Push(RESULT_SUCCESS);
199 rb.Push<u8>(0); 206 rb.Push<u8>(1);
200 } 207 }
201 Core::System& system; 208 Core::System& system;
202}; 209};
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 7dcdb4a07..f64535237 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -324,14 +324,14 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
324void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { 324void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
325 // Map backing memory for the font data 325 // Map backing memory for the font data
326 LOG_DEBUG(Service_NS, "called"); 326 LOG_DEBUG(Service_NS, "called");
327 Core::CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, 327 system.CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0,
328 SHARED_FONT_MEM_SIZE, 328 SHARED_FONT_MEM_SIZE,
329 Kernel::MemoryState::Shared); 329 Kernel::MemoryState::Shared);
330 330
331 // Create shared font memory object 331 // Create shared font memory object
332 auto& kernel = system.Kernel(); 332 auto& kernel = system.Kernel();
333 impl->shared_font_mem = Kernel::SharedMemory::Create( 333 impl->shared_font_mem = Kernel::SharedMemory::Create(
334 kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, 334 kernel, system.CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
335 Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, 335 Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
336 "PL_U:shared_font_mem"); 336 "PL_U:shared_font_mem");
337 337
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 6bc053f27..07c88465e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -45,6 +45,8 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std:
45 return GetVARegions(input, output); 45 return GetVARegions(input, output);
46 case IoctlCommand::IocUnmapBufferCommand: 46 case IoctlCommand::IocUnmapBufferCommand:
47 return UnmapBuffer(input, output); 47 return UnmapBuffer(input, output);
48 default:
49 break;
48 } 50 }
49 51
50 if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) 52 if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index ff6b1abae..eb88fee1b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -38,9 +38,10 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::v
38 return IocCtrlEventUnregister(input, output); 38 return IocCtrlEventUnregister(input, output);
39 case IoctlCommand::IocCtrlEventSignalCommand: 39 case IoctlCommand::IocCtrlEventSignalCommand:
40 return IocCtrlEventSignal(input, output); 40 return IocCtrlEventSignal(input, output);
41 default:
42 UNIMPLEMENTED_MSG("Unimplemented ioctl");
43 return 0;
41 } 44 }
42 UNIMPLEMENTED_MSG("Unimplemented ioctl");
43 return 0;
44} 45}
45 46
46u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { 47u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 389ace76f..cc2192e5c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -40,9 +40,10 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
40 return FlushL2(input, output); 40 return FlushL2(input, output);
41 case IoctlCommand::IocGetGpuTime: 41 case IoctlCommand::IocGetGpuTime:
42 return GetGpuTime(input, output); 42 return GetGpuTime(input, output);
43 default:
44 UNIMPLEMENTED_MSG("Unimplemented ioctl");
45 return 0;
43 } 46 }
44 UNIMPLEMENTED_MSG("Unimplemented ioctl");
45 return 0;
46} 47}
47 48
48u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, 49u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 2b8d1bef6..9de0ace22 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -44,6 +44,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
44 return GetWaitbase(input, output); 44 return GetWaitbase(input, output);
45 case IoctlCommand::IocChannelSetTimeoutCommand: 45 case IoctlCommand::IocChannelSetTimeoutCommand:
46 return ChannelSetTimeout(input, output); 46 return ChannelSetTimeout(input, output);
47 default:
48 break;
47 } 49 }
48 50
49 if (command.group == NVGPU_IOCTL_MAGIC) { 51 if (command.group == NVGPU_IOCTL_MAGIC) {
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index c12a746c8..7c5302017 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
208 AOC::InstallInterfaces(*sm, system); 208 AOC::InstallInterfaces(*sm, system);
209 APM::InstallInterfaces(system); 209 APM::InstallInterfaces(system);
210 Audio::InstallInterfaces(*sm, system); 210 Audio::InstallInterfaces(*sm, system);
211 BCAT::InstallInterfaces(*sm); 211 BCAT::InstallInterfaces(system);
212 BPC::InstallInterfaces(*sm); 212 BPC::InstallInterfaces(*sm);
213 BtDrv::InstallInterfaces(*sm, system); 213 BtDrv::InstallInterfaces(*sm, system);
214 BTM::InstallInterfaces(*sm, system); 214 BTM::InstallInterfaces(*sm, system);
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index e75c700ad..f629892ae 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -150,6 +150,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
150 // Apply cheats if they exist and the program has a valid title ID 150 // Apply cheats if they exist and the program has a valid title ID
151 if (pm) { 151 if (pm) {
152 auto& system = Core::System::GetInstance(); 152 auto& system = Core::System::GetInstance();
153 system.SetCurrentProcessBuildID(nso_header.build_id);
153 const auto cheats = pm->CreateCheatList(system, nso_header.build_id); 154 const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
154 if (!cheats.empty()) { 155 if (!cheats.empty()) {
155 system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); 156 system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 9e030789d..fa49f3dd0 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -146,7 +146,7 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
146 * using a VMA from the current process. 146 * using a VMA from the current process.
147 */ 147 */
148static u8* GetPointerFromVMA(VAddr vaddr) { 148static u8* GetPointerFromVMA(VAddr vaddr) {
149 return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); 149 return GetPointerFromVMA(*Core::System::GetInstance().CurrentProcess(), vaddr);
150} 150}
151 151
152template <typename T> 152template <typename T>
@@ -226,7 +226,7 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
226} 226}
227 227
228bool IsValidVirtualAddress(const VAddr vaddr) { 228bool IsValidVirtualAddress(const VAddr vaddr) {
229 return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr); 229 return IsValidVirtualAddress(*Core::System::GetInstance().CurrentProcess(), vaddr);
230} 230}
231 231
232bool IsKernelVirtualAddress(const VAddr vaddr) { 232bool IsKernelVirtualAddress(const VAddr vaddr) {
@@ -387,7 +387,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
387} 387}
388 388
389void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { 389void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
390 ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); 390 ReadBlock(*Core::System::GetInstance().CurrentProcess(), src_addr, dest_buffer, size);
391} 391}
392 392
393void Write8(const VAddr addr, const u8 data) { 393void Write8(const VAddr addr, const u8 data) {
@@ -450,7 +450,7 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
450} 450}
451 451
452void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { 452void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
453 WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); 453 WriteBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_buffer, size);
454} 454}
455 455
456void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) { 456void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) {
@@ -539,7 +539,7 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
539} 539}
540 540
541void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { 541void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) {
542 CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); 542 CopyBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_addr, size);
543} 543}
544 544
545} // namespace Memory 545} // namespace Memory
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 7de3fd1e5..d1fc94060 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -103,6 +103,8 @@ void LogSettings() {
103 LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub); 103 LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
104 LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port); 104 LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
105 LogSetting("Debugging_ProgramArgs", Settings::values.program_args); 105 LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
106 LogSetting("Services_BCATBackend", Settings::values.bcat_backend);
107 LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local);
106} 108}
107 109
108} // namespace Settings 110} // namespace Settings
diff --git a/src/core/settings.h b/src/core/settings.h
index 47bddfb30..9c98a9287 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -448,6 +448,10 @@ struct Values {
448 bool reporting_services; 448 bool reporting_services;
449 bool quest_flag; 449 bool quest_flag;
450 450
451 // BCAT
452 std::string bcat_backend;
453 bool bcat_boxcat_local;
454
451 // WebService 455 // WebService
452 bool enable_telemetry; 456 bool enable_telemetry;
453 std::string web_api_url; 457 std::string web_api_url;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index e2f85c5f1..eaa694ff8 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -105,9 +105,15 @@ add_library(video_core STATIC
105 shader/decode/warp.cpp 105 shader/decode/warp.cpp
106 shader/decode/xmad.cpp 106 shader/decode/xmad.cpp
107 shader/decode/other.cpp 107 shader/decode/other.cpp
108 shader/ast.cpp
109 shader/ast.h
108 shader/control_flow.cpp 110 shader/control_flow.cpp
109 shader/control_flow.h 111 shader/control_flow.h
112 shader/compiler_settings.cpp
113 shader/compiler_settings.h
110 shader/decode.cpp 114 shader/decode.cpp
115 shader/expr.cpp
116 shader/expr.h
111 shader/node_helper.cpp 117 shader/node_helper.cpp
112 shader/node_helper.h 118 shader/node_helper.h
113 shader/node.h 119 shader/node.h
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 28272ef6f..7a6355ce2 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -544,7 +544,7 @@ enum class VoteOperation : u64 {
544 Eq = 2, // allThreadsEqualNV 544 Eq = 2, // allThreadsEqualNV
545}; 545};
546 546
547enum class ImageAtomicSize : u64 { 547enum class ImageAtomicOperationType : u64 {
548 U32 = 0, 548 U32 = 0,
549 S32 = 1, 549 S32 = 1,
550 U64 = 2, 550 U64 = 2,
@@ -1432,11 +1432,11 @@ union Instruction {
1432 ASSERT(mode == SurfaceDataMode::D_BA); 1432 ASSERT(mode == SurfaceDataMode::D_BA);
1433 return store_data_layout; 1433 return store_data_layout;
1434 } 1434 }
1435 } sust; 1435 } suldst;
1436 1436
1437 union { 1437 union {
1438 BitField<28, 1, u64> is_ba; 1438 BitField<28, 1, u64> is_ba;
1439 BitField<51, 3, ImageAtomicSize> size; 1439 BitField<51, 3, ImageAtomicOperationType> operation_type;
1440 BitField<33, 3, ImageType> image_type; 1440 BitField<33, 3, ImageType> image_type;
1441 BitField<29, 4, ImageAtomicOperation> operation; 1441 BitField<29, 4, ImageAtomicOperation> operation;
1442 BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; 1442 BitField<49, 2, OutOfBoundsStore> out_of_bounds_store;
@@ -1595,6 +1595,7 @@ public:
1595 TMML_B, // Texture Mip Map Level 1595 TMML_B, // Texture Mip Map Level
1596 TMML, // Texture Mip Map Level 1596 TMML, // Texture Mip Map Level
1597 SUST, // Surface Store 1597 SUST, // Surface Store
1598 SULD, // Surface Load
1598 SUATOM, // Surface Atomic Operation 1599 SUATOM, // Surface Atomic Operation
1599 EXIT, 1600 EXIT,
1600 NOP, 1601 NOP,
@@ -1884,6 +1885,7 @@ private:
1884 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), 1885 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
1885 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), 1886 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
1886 INST("11101011001-----", Id::SUST, Type::Image, "SUST"), 1887 INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
1888 INST("11101011000-----", Id::SULD, Type::Image, "SULD"),
1887 INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"), 1889 INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),
1888 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), 1890 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"),
1889 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), 1891 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 4f59a87b4..64de7e425 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -2,8 +2,10 @@
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 <algorithm>
5#include <array> 6#include <array>
6#include <cstddef> 7#include <cstddef>
8#include <vector>
7#include <glad/glad.h> 9#include <glad/glad.h>
8 10
9#include "common/logging/log.h" 11#include "common/logging/log.h"
@@ -30,9 +32,27 @@ bool TestProgram(const GLchar* glsl) {
30 return link_status == GL_TRUE; 32 return link_status == GL_TRUE;
31} 33}
32 34
35std::vector<std::string_view> GetExtensions() {
36 GLint num_extensions;
37 glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
38 std::vector<std::string_view> extensions;
39 extensions.reserve(num_extensions);
40 for (GLint index = 0; index < num_extensions; ++index) {
41 extensions.push_back(
42 reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index))));
43 }
44 return extensions;
45}
46
47bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) {
48 return std::find(images.begin(), images.end(), extension) != images.end();
49}
50
33} // Anonymous namespace 51} // Anonymous namespace
34 52
35Device::Device() { 53Device::Device() {
54 const std::vector extensions = GetExtensions();
55
36 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); 56 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
37 shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); 57 shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
38 max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); 58 max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
@@ -40,6 +60,7 @@ Device::Device() {
40 has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && 60 has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
41 GLAD_GL_NV_shader_thread_shuffle; 61 GLAD_GL_NV_shader_thread_shuffle;
42 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; 62 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
63 has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
43 has_variable_aoffi = TestVariableAoffi(); 64 has_variable_aoffi = TestVariableAoffi();
44 has_component_indexing_bug = TestComponentIndexingBug(); 65 has_component_indexing_bug = TestComponentIndexingBug();
45 has_precise_bug = TestPreciseBug(); 66 has_precise_bug = TestPreciseBug();
@@ -55,6 +76,7 @@ Device::Device(std::nullptr_t) {
55 max_varyings = 15; 76 max_varyings = 15;
56 has_warp_intrinsics = true; 77 has_warp_intrinsics = true;
57 has_vertex_viewport_layer = true; 78 has_vertex_viewport_layer = true;
79 has_image_load_formatted = true;
58 has_variable_aoffi = true; 80 has_variable_aoffi = true;
59 has_component_indexing_bug = false; 81 has_component_indexing_bug = false;
60 has_precise_bug = false; 82 has_precise_bug = false;
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index ba6dcd3be..bb273c3d6 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -38,6 +38,10 @@ public:
38 return has_vertex_viewport_layer; 38 return has_vertex_viewport_layer;
39 } 39 }
40 40
41 bool HasImageLoadFormatted() const {
42 return has_image_load_formatted;
43 }
44
41 bool HasVariableAoffi() const { 45 bool HasVariableAoffi() const {
42 return has_variable_aoffi; 46 return has_variable_aoffi;
43 } 47 }
@@ -61,6 +65,7 @@ private:
61 u32 max_varyings{}; 65 u32 max_varyings{};
62 bool has_warp_intrinsics{}; 66 bool has_warp_intrinsics{};
63 bool has_vertex_viewport_layer{}; 67 bool has_vertex_viewport_layer{};
68 bool has_image_load_formatted{};
64 bool has_variable_aoffi{}; 69 bool has_variable_aoffi{};
65 bool has_component_indexing_bug{}; 70 bool has_component_indexing_bug{};
66 bool has_precise_bug{}; 71 bool has_precise_bug{};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 0dbc4c02f..42ca3b1bd 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -211,14 +211,14 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
211 const auto primitive_mode{variant.primitive_mode}; 211 const auto primitive_mode{variant.primitive_mode};
212 const auto texture_buffer_usage{variant.texture_buffer_usage}; 212 const auto texture_buffer_usage{variant.texture_buffer_usage};
213 213
214 std::string source = "#version 430 core\n" 214 std::string source = R"(#version 430 core
215 "#extension GL_ARB_separate_shader_objects : enable\n" 215#extension GL_ARB_separate_shader_objects : enable
216 "#extension GL_NV_gpu_shader5 : enable\n" 216#extension GL_ARB_shader_viewport_layer_array : enable
217 "#extension GL_NV_shader_thread_group : enable\n" 217#extension GL_EXT_shader_image_load_formatted : enable
218 "#extension GL_NV_shader_thread_shuffle : enable\n"; 218#extension GL_NV_gpu_shader5 : enable
219 if (entries.shader_viewport_layer_array) { 219#extension GL_NV_shader_thread_group : enable
220 source += "#extension GL_ARB_shader_viewport_layer_array : enable\n"; 220#extension GL_NV_shader_thread_shuffle : enable
221 } 221)";
222 if (program_type == ProgramType::Compute) { 222 if (program_type == ProgramType::Compute) {
223 source += "#extension GL_ARB_compute_variable_group_size : require\n"; 223 source += "#extension GL_ARB_compute_variable_group_size : require\n";
224 } 224 }
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 74cb59bc1..6a610a3bc 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -19,6 +19,8 @@
19#include "video_core/renderer_opengl/gl_device.h" 19#include "video_core/renderer_opengl/gl_device.h"
20#include "video_core/renderer_opengl/gl_rasterizer.h" 20#include "video_core/renderer_opengl/gl_rasterizer.h"
21#include "video_core/renderer_opengl/gl_shader_decompiler.h" 21#include "video_core/renderer_opengl/gl_shader_decompiler.h"
22#include "video_core/shader/ast.h"
23#include "video_core/shader/node.h"
22#include "video_core/shader/shader_ir.h" 24#include "video_core/shader/shader_ir.h"
23 25
24namespace OpenGL::GLShader { 26namespace OpenGL::GLShader {
@@ -241,6 +243,26 @@ constexpr const char* GetTypeString(Type type) {
241 } 243 }
242} 244}
243 245
246constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) {
247 switch (image_type) {
248 case Tegra::Shader::ImageType::Texture1D:
249 return "1D";
250 case Tegra::Shader::ImageType::TextureBuffer:
251 return "Buffer";
252 case Tegra::Shader::ImageType::Texture1DArray:
253 return "1DArray";
254 case Tegra::Shader::ImageType::Texture2D:
255 return "2D";
256 case Tegra::Shader::ImageType::Texture2DArray:
257 return "2DArray";
258 case Tegra::Shader::ImageType::Texture3D:
259 return "3D";
260 default:
261 UNREACHABLE();
262 return "1D";
263 }
264}
265
244/// Generates code to use for a swizzle operation. 266/// Generates code to use for a swizzle operation.
245constexpr const char* GetSwizzle(u32 element) { 267constexpr const char* GetSwizzle(u32 element) {
246 constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; 268 constexpr std::array swizzle = {".x", ".y", ".z", ".w"};
@@ -313,39 +335,24 @@ constexpr bool IsVertexShader(ProgramType stage) {
313 return stage == ProgramType::VertexA || stage == ProgramType::VertexB; 335 return stage == ProgramType::VertexA || stage == ProgramType::VertexB;
314} 336}
315 337
338class ASTDecompiler;
339class ExprDecompiler;
340
316class GLSLDecompiler final { 341class GLSLDecompiler final {
317public: 342public:
318 explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage, 343 explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage,
319 std::string suffix) 344 std::string suffix)
320 : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {} 345 : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
321 346
322 void Decompile() { 347 void DecompileBranchMode() {
323 DeclareVertex();
324 DeclareGeometry();
325 DeclareRegisters();
326 DeclarePredicates();
327 DeclareLocalMemory();
328 DeclareSharedMemory();
329 DeclareInternalFlags();
330 DeclareInputAttributes();
331 DeclareOutputAttributes();
332 DeclareConstantBuffers();
333 DeclareGlobalMemory();
334 DeclareSamplers();
335 DeclarePhysicalAttributeReader();
336 DeclareImages();
337
338 code.AddLine("void execute_{}() {{", suffix);
339 ++code.scope;
340
341 // VM's program counter 348 // VM's program counter
342 const auto first_address = ir.GetBasicBlocks().begin()->first; 349 const auto first_address = ir.GetBasicBlocks().begin()->first;
343 code.AddLine("uint jmp_to = {}U;", first_address); 350 code.AddLine("uint jmp_to = {}U;", first_address);
344 351
345 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems 352 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
346 // unlikely that shaders will use 20 nested SSYs and PBKs. 353 // unlikely that shaders will use 20 nested SSYs and PBKs.
354 constexpr u32 FLOW_STACK_SIZE = 20;
347 if (!ir.IsFlowStackDisabled()) { 355 if (!ir.IsFlowStackDisabled()) {
348 constexpr u32 FLOW_STACK_SIZE = 20;
349 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { 356 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
350 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); 357 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
351 code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); 358 code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
@@ -371,10 +378,37 @@ public:
371 code.AddLine("default: return;"); 378 code.AddLine("default: return;");
372 code.AddLine("}}"); 379 code.AddLine("}}");
373 380
374 for (std::size_t i = 0; i < 2; ++i) { 381 --code.scope;
375 --code.scope; 382 code.AddLine("}}");
376 code.AddLine("}}"); 383 }
384
385 void DecompileAST();
386
387 void Decompile() {
388 DeclareVertex();
389 DeclareGeometry();
390 DeclareRegisters();
391 DeclarePredicates();
392 DeclareLocalMemory();
393 DeclareInternalFlags();
394 DeclareInputAttributes();
395 DeclareOutputAttributes();
396 DeclareConstantBuffers();
397 DeclareGlobalMemory();
398 DeclareSamplers();
399 DeclarePhysicalAttributeReader();
400
401 code.AddLine("void execute_{}() {{", suffix);
402 ++code.scope;
403
404 if (ir.IsDecompiled()) {
405 DecompileAST();
406 } else {
407 DecompileBranchMode();
377 } 408 }
409
410 --code.scope;
411 code.AddLine("}}");
378 } 412 }
379 413
380 std::string GetResult() { 414 std::string GetResult() {
@@ -398,13 +432,14 @@ public:
398 usage.is_read, usage.is_written); 432 usage.is_read, usage.is_written);
399 } 433 }
400 entries.clip_distances = ir.GetClipDistances(); 434 entries.clip_distances = ir.GetClipDistances();
401 entries.shader_viewport_layer_array =
402 IsVertexShader(stage) && (ir.UsesLayer() || ir.UsesViewportIndex());
403 entries.shader_length = ir.GetLength(); 435 entries.shader_length = ir.GetLength();
404 return entries; 436 return entries;
405 } 437 }
406 438
407private: 439private:
440 friend class ASTDecompiler;
441 friend class ExprDecompiler;
442
408 void DeclareVertex() { 443 void DeclareVertex() {
409 if (!IsVertexShader(stage)) 444 if (!IsVertexShader(stage))
410 return; 445 return;
@@ -722,42 +757,6 @@ private:
722 void DeclareImages() { 757 void DeclareImages() {
723 const auto& images{ir.GetImages()}; 758 const auto& images{ir.GetImages()};
724 for (const auto& [offset, image] : images) { 759 for (const auto& [offset, image] : images) {
725 const char* image_type = [&] {
726 switch (image.GetType()) {
727 case Tegra::Shader::ImageType::Texture1D:
728 return "image1D";
729 case Tegra::Shader::ImageType::TextureBuffer:
730 return "imageBuffer";
731 case Tegra::Shader::ImageType::Texture1DArray:
732 return "image1DArray";
733 case Tegra::Shader::ImageType::Texture2D:
734 return "image2D";
735 case Tegra::Shader::ImageType::Texture2DArray:
736 return "image2DArray";
737 case Tegra::Shader::ImageType::Texture3D:
738 return "image3D";
739 default:
740 UNREACHABLE();
741 return "image1D";
742 }
743 }();
744
745 const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> {
746 if (!image.IsSizeKnown()) {
747 return {"", ""};
748 }
749 switch (image.GetSize()) {
750 case Tegra::Shader::ImageAtomicSize::U32:
751 return {"u", "r32ui, "};
752 case Tegra::Shader::ImageAtomicSize::S32:
753 return {"i", "r32i, "};
754 default:
755 UNIMPLEMENTED_MSG("Unimplemented atomic size={}",
756 static_cast<u32>(image.GetSize()));
757 return {"", ""};
758 }
759 }();
760
761 std::string qualifier = "coherent volatile"; 760 std::string qualifier = "coherent volatile";
762 if (image.IsRead() && !image.IsWritten()) { 761 if (image.IsRead() && !image.IsWritten()) {
763 qualifier += " readonly"; 762 qualifier += " readonly";
@@ -765,9 +764,10 @@ private:
765 qualifier += " writeonly"; 764 qualifier += " writeonly";
766 } 765 }
767 766
768 code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " 767 const char* format = image.IsAtomic() ? "r32ui, " : "";
769 "{} {};", 768 const char* type_declaration = GetImageTypeDeclaration(image.GetType());
770 image.GetIndex(), qualifier, image_type, GetImage(image)); 769 code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format,
770 image.GetIndex(), qualifier, type_declaration, GetImage(image));
771 } 771 }
772 if (!images.empty()) { 772 if (!images.empty()) {
773 code.AddNewLine(); 773 code.AddNewLine();
@@ -1234,28 +1234,13 @@ private:
1234 } 1234 }
1235 1235
1236 std::string BuildImageValues(Operation operation) { 1236 std::string BuildImageValues(Operation operation) {
1237 constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"};
1237 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1238 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1238 const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> {
1239 constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"};
1240 if (!meta.image.IsSizeKnown()) {
1241 return {float_constructors, Type::Float};
1242 }
1243 switch (meta.image.GetSize()) {
1244 case Tegra::Shader::ImageAtomicSize::U32:
1245 return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint};
1246 case Tegra::Shader::ImageAtomicSize::S32:
1247 return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint};
1248 default:
1249 UNIMPLEMENTED_MSG("Unimplemented image size={}",
1250 static_cast<u32>(meta.image.GetSize()));
1251 return {float_constructors, Type::Float};
1252 }
1253 }();
1254 1239
1255 const std::size_t values_count{meta.values.size()}; 1240 const std::size_t values_count{meta.values.size()};
1256 std::string expr = fmt::format("{}(", constructors.at(values_count - 1)); 1241 std::string expr = fmt::format("{}(", constructors.at(values_count - 1));
1257 for (std::size_t i = 0; i < values_count; ++i) { 1242 for (std::size_t i = 0; i < values_count; ++i) {
1258 expr += Visit(meta.values.at(i)).As(type); 1243 expr += Visit(meta.values.at(i)).AsUint();
1259 if (i + 1 < values_count) { 1244 if (i + 1 < values_count) {
1260 expr += ", "; 1245 expr += ", ";
1261 } 1246 }
@@ -1264,29 +1249,6 @@ private:
1264 return expr; 1249 return expr;
1265 } 1250 }
1266 1251
1267 Expression AtomicImage(Operation operation, const char* opname) {
1268 constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1269 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1270 ASSERT(meta.values.size() == 1);
1271 ASSERT(meta.image.IsSizeKnown());
1272
1273 const auto type = [&]() {
1274 switch (const auto size = meta.image.GetSize()) {
1275 case Tegra::Shader::ImageAtomicSize::U32:
1276 return Type::Uint;
1277 case Tegra::Shader::ImageAtomicSize::S32:
1278 return Type::Int;
1279 default:
1280 UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size));
1281 return Type::Uint;
1282 }
1283 }();
1284
1285 return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image),
1286 BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)),
1287 type};
1288 }
1289
1290 Expression Assign(Operation operation) { 1252 Expression Assign(Operation operation) {
1291 const Node& dest = operation[0]; 1253 const Node& dest = operation[0];
1292 const Node& src = operation[1]; 1254 const Node& src = operation[1];
@@ -1545,6 +1507,8 @@ private:
1545 case Tegra::Shader::HalfType::H1_H1: 1507 case Tegra::Shader::HalfType::H1_H1:
1546 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; 1508 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
1547 } 1509 }
1510 UNREACHABLE();
1511 return {"0", Type::Int};
1548 } 1512 }
1549 1513
1550 Expression HMergeF32(Operation operation) { 1514 Expression HMergeF32(Operation operation) {
@@ -1809,6 +1773,19 @@ private:
1809 return {tmp, Type::Float}; 1773 return {tmp, Type::Float};
1810 } 1774 }
1811 1775
1776 Expression ImageLoad(Operation operation) {
1777 if (!device.HasImageLoadFormatted()) {
1778 LOG_ERROR(Render_OpenGL,
1779 "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load");
1780 return {"0", Type::Int};
1781 }
1782
1783 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1784 return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image),
1785 BuildIntegerCoordinates(operation), GetSwizzle(meta.element)),
1786 Type::Uint};
1787 }
1788
1812 Expression ImageStore(Operation operation) { 1789 Expression ImageStore(Operation operation) {
1813 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1790 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1814 code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), 1791 code.AddLine("imageStore({}, {}, {});", GetImage(meta.image),
@@ -1816,31 +1793,14 @@ private:
1816 return {}; 1793 return {};
1817 } 1794 }
1818 1795
1819 Expression AtomicImageAdd(Operation operation) { 1796 template <const std::string_view& opname>
1820 return AtomicImage(operation, "imageAtomicAdd"); 1797 Expression AtomicImage(Operation operation) {
1821 } 1798 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1822 1799 ASSERT(meta.values.size() == 1);
1823 Expression AtomicImageMin(Operation operation) {
1824 return AtomicImage(operation, "imageAtomicMin");
1825 }
1826
1827 Expression AtomicImageMax(Operation operation) {
1828 return AtomicImage(operation, "imageAtomicMax");
1829 }
1830 Expression AtomicImageAnd(Operation operation) {
1831 return AtomicImage(operation, "imageAtomicAnd");
1832 }
1833
1834 Expression AtomicImageOr(Operation operation) {
1835 return AtomicImage(operation, "imageAtomicOr");
1836 }
1837
1838 Expression AtomicImageXor(Operation operation) {
1839 return AtomicImage(operation, "imageAtomicXor");
1840 }
1841 1800
1842 Expression AtomicImageExchange(Operation operation) { 1801 return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image),
1843 return AtomicImage(operation, "imageAtomicExchange"); 1802 BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()),
1803 Type::Uint};
1844 } 1804 }
1845 1805
1846 Expression Branch(Operation operation) { 1806 Expression Branch(Operation operation) {
@@ -1877,10 +1837,9 @@ private:
1877 return {}; 1837 return {};
1878 } 1838 }
1879 1839
1880 Expression Exit(Operation operation) { 1840 void PreExit() {
1881 if (stage != ProgramType::Fragment) { 1841 if (stage != ProgramType::Fragment) {
1882 code.AddLine("return;"); 1842 return;
1883 return {};
1884 } 1843 }
1885 const auto& used_registers = ir.GetRegisters(); 1844 const auto& used_registers = ir.GetRegisters();
1886 const auto SafeGetRegister = [&](u32 reg) -> Expression { 1845 const auto SafeGetRegister = [&](u32 reg) -> Expression {
@@ -1912,7 +1871,10 @@ private:
1912 // already contains one past the last color register. 1871 // already contains one past the last color register.
1913 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); 1872 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
1914 } 1873 }
1874 }
1915 1875
1876 Expression Exit(Operation operation) {
1877 PreExit();
1916 code.AddLine("return;"); 1878 code.AddLine("return;");
1917 return {}; 1879 return {};
1918 } 1880 }
@@ -2035,6 +1997,12 @@ private:
2035 Func() = delete; 1997 Func() = delete;
2036 ~Func() = delete; 1998 ~Func() = delete;
2037 1999
2000 static constexpr std::string_view Add = "Add";
2001 static constexpr std::string_view And = "And";
2002 static constexpr std::string_view Or = "Or";
2003 static constexpr std::string_view Xor = "Xor";
2004 static constexpr std::string_view Exchange = "Exchange";
2005
2038 static constexpr std::string_view ShuffleIndexed = "shuffleNV"; 2006 static constexpr std::string_view ShuffleIndexed = "shuffleNV";
2039 static constexpr std::string_view ShuffleUp = "shuffleUpNV"; 2007 static constexpr std::string_view ShuffleUp = "shuffleUpNV";
2040 static constexpr std::string_view ShuffleDown = "shuffleDownNV"; 2008 static constexpr std::string_view ShuffleDown = "shuffleDownNV";
@@ -2172,14 +2140,14 @@ private:
2172 &GLSLDecompiler::TextureQueryLod, 2140 &GLSLDecompiler::TextureQueryLod,
2173 &GLSLDecompiler::TexelFetch, 2141 &GLSLDecompiler::TexelFetch,
2174 2142
2143 &GLSLDecompiler::ImageLoad,
2175 &GLSLDecompiler::ImageStore, 2144 &GLSLDecompiler::ImageStore,
2176 &GLSLDecompiler::AtomicImageAdd, 2145
2177 &GLSLDecompiler::AtomicImageMin, 2146 &GLSLDecompiler::AtomicImage<Func::Add>,
2178 &GLSLDecompiler::AtomicImageMax, 2147 &GLSLDecompiler::AtomicImage<Func::And>,
2179 &GLSLDecompiler::AtomicImageAnd, 2148 &GLSLDecompiler::AtomicImage<Func::Or>,
2180 &GLSLDecompiler::AtomicImageOr, 2149 &GLSLDecompiler::AtomicImage<Func::Xor>,
2181 &GLSLDecompiler::AtomicImageXor, 2150 &GLSLDecompiler::AtomicImage<Func::Exchange>,
2182 &GLSLDecompiler::AtomicImageExchange,
2183 2151
2184 &GLSLDecompiler::Branch, 2152 &GLSLDecompiler::Branch,
2185 &GLSLDecompiler::BranchIndirect, 2153 &GLSLDecompiler::BranchIndirect,
@@ -2303,6 +2271,208 @@ private:
2303 ShaderWriter code; 2271 ShaderWriter code;
2304}; 2272};
2305 2273
2274static constexpr std::string_view flow_var = "flow_var_";
2275
2276std::string GetFlowVariable(u32 i) {
2277 return fmt::format("{}{}", flow_var, i);
2278}
2279
2280class ExprDecompiler {
2281public:
2282 explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
2283
2284 void operator()(VideoCommon::Shader::ExprAnd& expr) {
2285 inner += "( ";
2286 std::visit(*this, *expr.operand1);
2287 inner += " && ";
2288 std::visit(*this, *expr.operand2);
2289 inner += ')';
2290 }
2291
2292 void operator()(VideoCommon::Shader::ExprOr& expr) {
2293 inner += "( ";
2294 std::visit(*this, *expr.operand1);
2295 inner += " || ";
2296 std::visit(*this, *expr.operand2);
2297 inner += ')';
2298 }
2299
2300 void operator()(VideoCommon::Shader::ExprNot& expr) {
2301 inner += '!';
2302 std::visit(*this, *expr.operand1);
2303 }
2304
2305 void operator()(VideoCommon::Shader::ExprPredicate& expr) {
2306 const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
2307 inner += decomp.GetPredicate(pred);
2308 }
2309
2310 void operator()(VideoCommon::Shader::ExprCondCode& expr) {
2311 const Node cc = decomp.ir.GetConditionCode(expr.cc);
2312 std::string target;
2313
2314 if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
2315 const auto index = pred->GetIndex();
2316 switch (index) {
2317 case Tegra::Shader::Pred::NeverExecute:
2318 target = "false";
2319 case Tegra::Shader::Pred::UnusedIndex:
2320 target = "true";
2321 default:
2322 target = decomp.GetPredicate(index);
2323 }
2324 } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
2325 target = decomp.GetInternalFlag(flag->GetFlag());
2326 } else {
2327 UNREACHABLE();
2328 }
2329 inner += target;
2330 }
2331
2332 void operator()(VideoCommon::Shader::ExprVar& expr) {
2333 inner += GetFlowVariable(expr.var_index);
2334 }
2335
2336 void operator()(VideoCommon::Shader::ExprBoolean& expr) {
2337 inner += expr.value ? "true" : "false";
2338 }
2339
2340 std::string& GetResult() {
2341 return inner;
2342 }
2343
2344private:
2345 std::string inner;
2346 GLSLDecompiler& decomp;
2347};
2348
2349class ASTDecompiler {
2350public:
2351 explicit ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
2352
2353 void operator()(VideoCommon::Shader::ASTProgram& ast) {
2354 ASTNode current = ast.nodes.GetFirst();
2355 while (current) {
2356 Visit(current);
2357 current = current->GetNext();
2358 }
2359 }
2360
2361 void operator()(VideoCommon::Shader::ASTIfThen& ast) {
2362 ExprDecompiler expr_parser{decomp};
2363 std::visit(expr_parser, *ast.condition);
2364 decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
2365 decomp.code.scope++;
2366 ASTNode current = ast.nodes.GetFirst();
2367 while (current) {
2368 Visit(current);
2369 current = current->GetNext();
2370 }
2371 decomp.code.scope--;
2372 decomp.code.AddLine("}}");
2373 }
2374
2375 void operator()(VideoCommon::Shader::ASTIfElse& ast) {
2376 decomp.code.AddLine("else {{");
2377 decomp.code.scope++;
2378 ASTNode current = ast.nodes.GetFirst();
2379 while (current) {
2380 Visit(current);
2381 current = current->GetNext();
2382 }
2383 decomp.code.scope--;
2384 decomp.code.AddLine("}}");
2385 }
2386
2387 void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
2388 UNREACHABLE();
2389 }
2390
2391 void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
2392 decomp.VisitBlock(ast.nodes);
2393 }
2394
2395 void operator()(VideoCommon::Shader::ASTVarSet& ast) {
2396 ExprDecompiler expr_parser{decomp};
2397 std::visit(expr_parser, *ast.condition);
2398 decomp.code.AddLine("{} = {};", GetFlowVariable(ast.index), expr_parser.GetResult());
2399 }
2400
2401 void operator()(VideoCommon::Shader::ASTLabel& ast) {
2402 decomp.code.AddLine("// Label_{}:", ast.index);
2403 }
2404
2405 void operator()(VideoCommon::Shader::ASTGoto& ast) {
2406 UNREACHABLE();
2407 }
2408
2409 void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
2410 ExprDecompiler expr_parser{decomp};
2411 std::visit(expr_parser, *ast.condition);
2412 decomp.code.AddLine("do {{");
2413 decomp.code.scope++;
2414 ASTNode current = ast.nodes.GetFirst();
2415 while (current) {
2416 Visit(current);
2417 current = current->GetNext();
2418 }
2419 decomp.code.scope--;
2420 decomp.code.AddLine("}} while({});", expr_parser.GetResult());
2421 }
2422
2423 void operator()(VideoCommon::Shader::ASTReturn& ast) {
2424 const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
2425 if (!is_true) {
2426 ExprDecompiler expr_parser{decomp};
2427 std::visit(expr_parser, *ast.condition);
2428 decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
2429 decomp.code.scope++;
2430 }
2431 if (ast.kills) {
2432 decomp.code.AddLine("discard;");
2433 } else {
2434 decomp.PreExit();
2435 decomp.code.AddLine("return;");
2436 }
2437 if (!is_true) {
2438 decomp.code.scope--;
2439 decomp.code.AddLine("}}");
2440 }
2441 }
2442
2443 void operator()(VideoCommon::Shader::ASTBreak& ast) {
2444 const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
2445 if (!is_true) {
2446 ExprDecompiler expr_parser{decomp};
2447 std::visit(expr_parser, *ast.condition);
2448 decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
2449 decomp.code.scope++;
2450 }
2451 decomp.code.AddLine("break;");
2452 if (!is_true) {
2453 decomp.code.scope--;
2454 decomp.code.AddLine("}}");
2455 }
2456 }
2457
2458 void Visit(VideoCommon::Shader::ASTNode& node) {
2459 std::visit(*this, *node->GetInnerData());
2460 }
2461
2462private:
2463 GLSLDecompiler& decomp;
2464};
2465
2466void GLSLDecompiler::DecompileAST() {
2467 const u32 num_flow_variables = ir.GetASTNumVariables();
2468 for (u32 i = 0; i < num_flow_variables; i++) {
2469 code.AddLine("bool {} = false;", GetFlowVariable(i));
2470 }
2471 ASTDecompiler decompiler{*this};
2472 VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
2473 decompiler.Visit(program);
2474}
2475
2306} // Anonymous namespace 2476} // Anonymous namespace
2307 2477
2308std::string GetCommonDeclarations() { 2478std::string GetCommonDeclarations() {
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index 2ea02f5bf..e538dc001 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -90,7 +90,6 @@ struct ShaderEntries {
90 std::vector<ImageEntry> images; 90 std::vector<ImageEntry> images;
91 std::vector<GlobalMemoryEntry> global_memory_entries; 91 std::vector<GlobalMemoryEntry> global_memory_entries;
92 std::array<bool, Maxwell::NumClipDistances> clip_distances{}; 92 std::array<bool, Maxwell::NumClipDistances> clip_distances{};
93 bool shader_viewport_layer_array{};
94 std::size_t shader_length{}; 93 std::size_t shader_length{};
95}; 94};
96 95
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 f141c4e3b..74cc33476 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -112,14 +112,15 @@ std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskC
112ShaderDiskCacheOpenGL::LoadTransferable() { 112ShaderDiskCacheOpenGL::LoadTransferable() {
113 // Skip games without title id 113 // Skip games without title id
114 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; 114 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
115 if (!Settings::values.use_disk_shader_cache || !has_title_id) 115 if (!Settings::values.use_disk_shader_cache || !has_title_id) {
116 return {}; 116 return {};
117 tried_to_load = true; 117 }
118 118
119 FileUtil::IOFile file(GetTransferablePath(), "rb"); 119 FileUtil::IOFile file(GetTransferablePath(), "rb");
120 if (!file.IsOpen()) { 120 if (!file.IsOpen()) {
121 LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}", 121 LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}",
122 GetTitleID()); 122 GetTitleID());
123 is_usable = true;
123 return {}; 124 return {};
124 } 125 }
125 126
@@ -135,6 +136,7 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
135 LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing"); 136 LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing");
136 file.Close(); 137 file.Close();
137 InvalidateTransferable(); 138 InvalidateTransferable();
139 is_usable = true;
138 return {}; 140 return {};
139 } 141 }
140 if (version > NativeVersion) { 142 if (version > NativeVersion) {
@@ -180,13 +182,15 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
180 } 182 }
181 } 183 }
182 184
183 return {{raws, usages}}; 185 is_usable = true;
186 return {{std::move(raws), std::move(usages)}};
184} 187}
185 188
186std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap> 189std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
187ShaderDiskCacheOpenGL::LoadPrecompiled() { 190ShaderDiskCacheOpenGL::LoadPrecompiled() {
188 if (!IsUsable()) 191 if (!is_usable) {
189 return {}; 192 return {};
193 }
190 194
191 FileUtil::IOFile file(GetPrecompiledPath(), "rb"); 195 FileUtil::IOFile file(GetPrecompiledPath(), "rb");
192 if (!file.IsOpen()) { 196 if (!file.IsOpen()) {
@@ -343,20 +347,17 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
343 u8 is_bindless{}; 347 u8 is_bindless{};
344 u8 is_written{}; 348 u8 is_written{};
345 u8 is_read{}; 349 u8 is_read{};
346 u8 is_size_known{}; 350 u8 is_atomic{};
347 u32 size{};
348 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || 351 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
349 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || 352 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
350 !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || 353 !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) ||
351 !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) { 354 !LoadObjectFromPrecompiled(is_atomic)) {
352 return {}; 355 return {};
353 } 356 }
354 entry.entries.images.emplace_back( 357 entry.entries.images.emplace_back(
355 static_cast<std::size_t>(offset), static_cast<std::size_t>(index), 358 static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
356 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0, 359 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0,
357 is_read != 0, 360 is_read != 0, is_atomic != 0);
358 is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size))
359 : std::nullopt);
360 } 361 }
361 362
362 u32 global_memory_count{}; 363 u32 global_memory_count{};
@@ -382,12 +383,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
382 } 383 }
383 } 384 }
384 385
385 bool shader_viewport_layer_array{};
386 if (!LoadObjectFromPrecompiled(shader_viewport_layer_array)) {
387 return {};
388 }
389 entry.entries.shader_viewport_layer_array = shader_viewport_layer_array;
390
391 u64 shader_length{}; 386 u64 shader_length{};
392 if (!LoadObjectFromPrecompiled(shader_length)) { 387 if (!LoadObjectFromPrecompiled(shader_length)) {
393 return {}; 388 return {};
@@ -435,14 +430,13 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
435 return false; 430 return false;
436 } 431 }
437 for (const auto& image : entries.images) { 432 for (const auto& image : entries.images) {
438 const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U;
439 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || 433 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
440 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || 434 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
441 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || 435 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
442 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) || 436 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
443 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) || 437 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||
444 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || 438 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
445 !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) { 439 !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) {
446 return false; 440 return false;
447 } 441 }
448 } 442 }
@@ -464,10 +458,6 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
464 } 458 }
465 } 459 }
466 460
467 if (!SaveObjectToPrecompiled(entries.shader_viewport_layer_array)) {
468 return false;
469 }
470
471 if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) { 461 if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
472 return false; 462 return false;
473 } 463 }
@@ -493,8 +483,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
493} 483}
494 484
495void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) { 485void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {
496 if (!IsUsable()) 486 if (!is_usable) {
497 return; 487 return;
488 }
498 489
499 const u64 id = entry.GetUniqueIdentifier(); 490 const u64 id = entry.GetUniqueIdentifier();
500 if (transferable.find(id) != transferable.end()) { 491 if (transferable.find(id) != transferable.end()) {
@@ -515,8 +506,9 @@ void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {
515} 506}
516 507
517void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) { 508void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
518 if (!IsUsable()) 509 if (!is_usable) {
519 return; 510 return;
511 }
520 512
521 const auto it = transferable.find(usage.unique_identifier); 513 const auto it = transferable.find(usage.unique_identifier);
522 ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously"); 514 ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously");
@@ -542,8 +534,9 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
542 534
543void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code, 535void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code,
544 const GLShader::ShaderEntries& entries) { 536 const GLShader::ShaderEntries& entries) {
545 if (!IsUsable()) 537 if (!is_usable) {
546 return; 538 return;
539 }
547 540
548 if (precompiled_cache_virtual_file.GetSize() == 0) { 541 if (precompiled_cache_virtual_file.GetSize() == 0) {
549 SavePrecompiledHeaderToVirtualPrecompiledCache(); 542 SavePrecompiledHeaderToVirtualPrecompiledCache();
@@ -557,8 +550,9 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
557} 550}
558 551
559void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint program) { 552void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint program) {
560 if (!IsUsable()) 553 if (!is_usable) {
561 return; 554 return;
555 }
562 556
563 GLint binary_length{}; 557 GLint binary_length{};
564 glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length); 558 glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length);
@@ -579,10 +573,6 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
579 } 573 }
580} 574}
581 575
582bool ShaderDiskCacheOpenGL::IsUsable() const {
583 return tried_to_load && Settings::values.use_disk_shader_cache;
584}
585
586FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const { 576FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
587 if (!EnsureDirectories()) 577 if (!EnsureDirectories())
588 return {}; 578 return {};
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 cc8bbd61e..9595bd71b 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -224,9 +224,6 @@ private:
224 bool SaveDecompiledFile(u64 unique_identifier, const std::string& code, 224 bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,
225 const GLShader::ShaderEntries& entries); 225 const GLShader::ShaderEntries& entries);
226 226
227 /// Returns if the cache can be used
228 bool IsUsable() const;
229
230 /// Opens current game's transferable file and write it's header if it doesn't exist 227 /// Opens current game's transferable file and write it's header if it doesn't exist
231 FileUtil::IOFile AppendTransferableFile() const; 228 FileUtil::IOFile AppendTransferableFile() const;
232 229
@@ -297,7 +294,7 @@ private:
297 std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable; 294 std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
298 295
299 // The cache has been loaded at boot 296 // The cache has been loaded at boot
300 bool tried_to_load{}; 297 bool is_usable{};
301}; 298};
302 299
303} // namespace OpenGL 300} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 3a8d9e1da..b5a43e79e 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -11,12 +11,16 @@
11namespace OpenGL::GLShader { 11namespace OpenGL::GLShader {
12 12
13using Tegra::Engines::Maxwell3D; 13using Tegra::Engines::Maxwell3D;
14using VideoCommon::Shader::CompileDepth;
15using VideoCommon::Shader::CompilerSettings;
14using VideoCommon::Shader::ProgramCode; 16using VideoCommon::Shader::ProgramCode;
15using VideoCommon::Shader::ShaderIR; 17using VideoCommon::Shader::ShaderIR;
16 18
17static constexpr u32 PROGRAM_OFFSET = 10; 19static constexpr u32 PROGRAM_OFFSET = 10;
18static constexpr u32 COMPUTE_OFFSET = 0; 20static constexpr u32 COMPUTE_OFFSET = 0;
19 21
22static constexpr CompilerSettings settings{CompileDepth::NoFlowStack, true};
23
20ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) { 24ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
21 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); 25 const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
22 26
@@ -31,13 +35,14 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
31 35
32)"; 36)";
33 37
34 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); 38 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
35 const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB; 39 const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB;
36 ProgramResult program = Decompile(device, program_ir, stage, "vertex"); 40 ProgramResult program = Decompile(device, program_ir, stage, "vertex");
37 out += program.first; 41 out += program.first;
38 42
39 if (setup.IsDualProgram()) { 43 if (setup.IsDualProgram()) {
40 const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b); 44 const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b,
45 settings);
41 ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b"); 46 ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b");
42 out += program_b.first; 47 out += program_b.first;
43 } 48 }
@@ -80,7 +85,7 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
80 85
81)"; 86)";
82 87
83 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); 88 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
84 ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry"); 89 ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry");
85 out += program.first; 90 out += program.first;
86 91
@@ -114,7 +119,8 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
114}; 119};
115 120
116)"; 121)";
117 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); 122
123 const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
118 ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment"); 124 ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment");
119 out += program.first; 125 out += program.first;
120 126
@@ -133,7 +139,7 @@ ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& set
133 std::string out = "// Shader Unique Id: CS" + id + "\n\n"; 139 std::string out = "// Shader Unique Id: CS" + id + "\n\n";
134 out += GetCommonDeclarations(); 140 out += GetCommonDeclarations();
135 141
136 const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a); 142 const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a, settings);
137 ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute"); 143 ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute");
138 out += program.first; 144 out += program.first;
139 145
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index f7fbbb6e4..8bcd04221 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -19,6 +19,7 @@
19#include "video_core/engines/shader_header.h" 19#include "video_core/engines/shader_header.h"
20#include "video_core/renderer_vulkan/vk_device.h" 20#include "video_core/renderer_vulkan/vk_device.h"
21#include "video_core/renderer_vulkan/vk_shader_decompiler.h" 21#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
22#include "video_core/shader/node.h"
22#include "video_core/shader/shader_ir.h" 23#include "video_core/shader/shader_ir.h"
23 24
24namespace Vulkan::VKShader { 25namespace Vulkan::VKShader {
@@ -87,6 +88,9 @@ bool IsPrecise(Operation operand) {
87 88
88} // namespace 89} // namespace
89 90
91class ASTDecompiler;
92class ExprDecompiler;
93
90class SPIRVDecompiler : public Sirit::Module { 94class SPIRVDecompiler : public Sirit::Module {
91public: 95public:
92 explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage) 96 explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage)
@@ -96,27 +100,7 @@ public:
96 AddExtension("SPV_KHR_variable_pointers"); 100 AddExtension("SPV_KHR_variable_pointers");
97 } 101 }
98 102
99 void Decompile() { 103 void DecompileBranchMode() {
100 AllocateBindings();
101 AllocateLabels();
102
103 DeclareVertex();
104 DeclareGeometry();
105 DeclareFragment();
106 DeclareRegisters();
107 DeclarePredicates();
108 DeclareLocalMemory();
109 DeclareInternalFlags();
110 DeclareInputAttributes();
111 DeclareOutputAttributes();
112 DeclareConstantBuffers();
113 DeclareGlobalBuffers();
114 DeclareSamplers();
115
116 execute_function =
117 Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
118 Emit(OpLabel());
119
120 const u32 first_address = ir.GetBasicBlocks().begin()->first; 104 const u32 first_address = ir.GetBasicBlocks().begin()->first;
121 const Id loop_label = OpLabel("loop"); 105 const Id loop_label = OpLabel("loop");
122 const Id merge_label = OpLabel("merge"); 106 const Id merge_label = OpLabel("merge");
@@ -173,6 +157,43 @@ public:
173 Emit(continue_label); 157 Emit(continue_label);
174 Emit(OpBranch(loop_label)); 158 Emit(OpBranch(loop_label));
175 Emit(merge_label); 159 Emit(merge_label);
160 }
161
162 void DecompileAST();
163
164 void Decompile() {
165 const bool is_fully_decompiled = ir.IsDecompiled();
166 AllocateBindings();
167 if (!is_fully_decompiled) {
168 AllocateLabels();
169 }
170
171 DeclareVertex();
172 DeclareGeometry();
173 DeclareFragment();
174 DeclareRegisters();
175 DeclarePredicates();
176 if (is_fully_decompiled) {
177 DeclareFlowVariables();
178 }
179 DeclareLocalMemory();
180 DeclareInternalFlags();
181 DeclareInputAttributes();
182 DeclareOutputAttributes();
183 DeclareConstantBuffers();
184 DeclareGlobalBuffers();
185 DeclareSamplers();
186
187 execute_function =
188 Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
189 Emit(OpLabel());
190
191 if (is_fully_decompiled) {
192 DecompileAST();
193 } else {
194 DecompileBranchMode();
195 }
196
176 Emit(OpReturn()); 197 Emit(OpReturn());
177 Emit(OpFunctionEnd()); 198 Emit(OpFunctionEnd());
178 } 199 }
@@ -205,6 +226,9 @@ public:
205 } 226 }
206 227
207private: 228private:
229 friend class ASTDecompiler;
230 friend class ExprDecompiler;
231
208 static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount); 232 static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
209 233
210 void AllocateBindings() { 234 void AllocateBindings() {
@@ -293,6 +317,14 @@ private:
293 } 317 }
294 } 318 }
295 319
320 void DeclareFlowVariables() {
321 for (u32 i = 0; i < ir.GetASTNumVariables(); i++) {
322 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
323 Name(id, fmt::format("flow_var_{}", static_cast<u32>(i)));
324 flow_variables.emplace(i, AddGlobalVariable(id));
325 }
326 }
327
296 void DeclareLocalMemory() { 328 void DeclareLocalMemory() {
297 if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) { 329 if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
298 const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4); 330 const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
@@ -614,9 +646,15 @@ private:
614 Emit(OpBranchConditional(condition, true_label, skip_label)); 646 Emit(OpBranchConditional(condition, true_label, skip_label));
615 Emit(true_label); 647 Emit(true_label);
616 648
649 ++conditional_nest_count;
617 VisitBasicBlock(conditional->GetCode()); 650 VisitBasicBlock(conditional->GetCode());
651 --conditional_nest_count;
618 652
619 Emit(OpBranch(skip_label)); 653 if (inside_branch == 0) {
654 Emit(OpBranch(skip_label));
655 } else {
656 inside_branch--;
657 }
620 Emit(skip_label); 658 Emit(skip_label);
621 return {}; 659 return {};
622 660
@@ -939,22 +977,17 @@ private:
939 return {}; 977 return {};
940 } 978 }
941 979
942 Id ImageStore(Operation operation) { 980 Id ImageLoad(Operation operation) {
943 UNIMPLEMENTED();
944 return {};
945 }
946
947 Id AtomicImageAdd(Operation operation) {
948 UNIMPLEMENTED(); 981 UNIMPLEMENTED();
949 return {}; 982 return {};
950 } 983 }
951 984
952 Id AtomicImageMin(Operation operation) { 985 Id ImageStore(Operation operation) {
953 UNIMPLEMENTED(); 986 UNIMPLEMENTED();
954 return {}; 987 return {};
955 } 988 }
956 989
957 Id AtomicImageMax(Operation operation) { 990 Id AtomicImageAdd(Operation operation) {
958 UNIMPLEMENTED(); 991 UNIMPLEMENTED();
959 return {}; 992 return {};
960 } 993 }
@@ -984,7 +1017,11 @@ private:
984 UNIMPLEMENTED_IF(!target); 1017 UNIMPLEMENTED_IF(!target);
985 1018
986 Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue()))); 1019 Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
987 BranchingOp([&]() { Emit(OpBranch(continue_label)); }); 1020 Emit(OpBranch(continue_label));
1021 inside_branch = conditional_nest_count;
1022 if (conditional_nest_count == 0) {
1023 Emit(OpLabel());
1024 }
988 return {}; 1025 return {};
989 } 1026 }
990 1027
@@ -992,7 +1029,11 @@ private:
992 const Id op_a = VisitOperand<Type::Uint>(operation, 0); 1029 const Id op_a = VisitOperand<Type::Uint>(operation, 0);
993 1030
994 Emit(OpStore(jmp_to, op_a)); 1031 Emit(OpStore(jmp_to, op_a));
995 BranchingOp([&]() { Emit(OpBranch(continue_label)); }); 1032 Emit(OpBranch(continue_label));
1033 inside_branch = conditional_nest_count;
1034 if (conditional_nest_count == 0) {
1035 Emit(OpLabel());
1036 }
996 return {}; 1037 return {};
997 } 1038 }
998 1039
@@ -1019,11 +1060,15 @@ private:
1019 1060
1020 Emit(OpStore(flow_stack_top, previous)); 1061 Emit(OpStore(flow_stack_top, previous));
1021 Emit(OpStore(jmp_to, target)); 1062 Emit(OpStore(jmp_to, target));
1022 BranchingOp([&]() { Emit(OpBranch(continue_label)); }); 1063 Emit(OpBranch(continue_label));
1064 inside_branch = conditional_nest_count;
1065 if (conditional_nest_count == 0) {
1066 Emit(OpLabel());
1067 }
1023 return {}; 1068 return {};
1024 } 1069 }
1025 1070
1026 Id Exit(Operation operation) { 1071 Id PreExit() {
1027 switch (stage) { 1072 switch (stage) {
1028 case ShaderStage::Vertex: { 1073 case ShaderStage::Vertex: {
1029 // TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't 1074 // TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
@@ -1071,12 +1116,35 @@ private:
1071 } 1116 }
1072 } 1117 }
1073 1118
1074 BranchingOp([&]() { Emit(OpReturn()); }); 1119 return {};
1120 }
1121
1122 Id Exit(Operation operation) {
1123 PreExit();
1124 inside_branch = conditional_nest_count;
1125 if (conditional_nest_count > 0) {
1126 Emit(OpReturn());
1127 } else {
1128 const Id dummy = OpLabel();
1129 Emit(OpBranch(dummy));
1130 Emit(dummy);
1131 Emit(OpReturn());
1132 Emit(OpLabel());
1133 }
1075 return {}; 1134 return {};
1076 } 1135 }
1077 1136
1078 Id Discard(Operation operation) { 1137 Id Discard(Operation operation) {
1079 BranchingOp([&]() { Emit(OpKill()); }); 1138 inside_branch = conditional_nest_count;
1139 if (conditional_nest_count > 0) {
1140 Emit(OpKill());
1141 } else {
1142 const Id dummy = OpLabel();
1143 Emit(OpBranch(dummy));
1144 Emit(dummy);
1145 Emit(OpKill());
1146 Emit(OpLabel());
1147 }
1080 return {}; 1148 return {};
1081 } 1149 }
1082 1150
@@ -1271,17 +1339,6 @@ private:
1271 return {}; 1339 return {};
1272 } 1340 }
1273 1341
1274 void BranchingOp(std::function<void()> call) {
1275 const Id true_label = OpLabel();
1276 const Id skip_label = OpLabel();
1277 Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
1278 Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
1279 Emit(true_label);
1280 call();
1281
1282 Emit(skip_label);
1283 }
1284
1285 std::tuple<Id, Id> CreateFlowStack() { 1342 std::tuple<Id, Id> CreateFlowStack() {
1286 // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely 1343 // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
1287 // that shaders will use 20 nested SSYs and PBKs. 1344 // that shaders will use 20 nested SSYs and PBKs.
@@ -1440,10 +1497,9 @@ private:
1440 &SPIRVDecompiler::TextureQueryLod, 1497 &SPIRVDecompiler::TextureQueryLod,
1441 &SPIRVDecompiler::TexelFetch, 1498 &SPIRVDecompiler::TexelFetch,
1442 1499
1500 &SPIRVDecompiler::ImageLoad,
1443 &SPIRVDecompiler::ImageStore, 1501 &SPIRVDecompiler::ImageStore,
1444 &SPIRVDecompiler::AtomicImageAdd, 1502 &SPIRVDecompiler::AtomicImageAdd,
1445 &SPIRVDecompiler::AtomicImageMin,
1446 &SPIRVDecompiler::AtomicImageMax,
1447 &SPIRVDecompiler::AtomicImageAnd, 1503 &SPIRVDecompiler::AtomicImageAnd,
1448 &SPIRVDecompiler::AtomicImageOr, 1504 &SPIRVDecompiler::AtomicImageOr,
1449 &SPIRVDecompiler::AtomicImageXor, 1505 &SPIRVDecompiler::AtomicImageXor,
@@ -1488,6 +1544,8 @@ private:
1488 const ShaderIR& ir; 1544 const ShaderIR& ir;
1489 const ShaderStage stage; 1545 const ShaderStage stage;
1490 const Tegra::Shader::Header header; 1546 const Tegra::Shader::Header header;
1547 u64 conditional_nest_count{};
1548 u64 inside_branch{};
1491 1549
1492 const Id t_void = Name(TypeVoid(), "void"); 1550 const Id t_void = Name(TypeVoid(), "void");
1493 1551
@@ -1550,6 +1608,7 @@ private:
1550 Id per_vertex{}; 1608 Id per_vertex{};
1551 std::map<u32, Id> registers; 1609 std::map<u32, Id> registers;
1552 std::map<Tegra::Shader::Pred, Id> predicates; 1610 std::map<Tegra::Shader::Pred, Id> predicates;
1611 std::map<u32, Id> flow_variables;
1553 Id local_memory{}; 1612 Id local_memory{};
1554 std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{}; 1613 std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
1555 std::map<Attribute::Index, Id> input_attributes; 1614 std::map<Attribute::Index, Id> input_attributes;
@@ -1585,6 +1644,223 @@ private:
1585 std::map<u32, Id> labels; 1644 std::map<u32, Id> labels;
1586}; 1645};
1587 1646
1647class ExprDecompiler {
1648public:
1649 explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
1650
1651 Id operator()(VideoCommon::Shader::ExprAnd& expr) {
1652 const Id type_def = decomp.GetTypeDefinition(Type::Bool);
1653 const Id op1 = Visit(expr.operand1);
1654 const Id op2 = Visit(expr.operand2);
1655 return decomp.Emit(decomp.OpLogicalAnd(type_def, op1, op2));
1656 }
1657
1658 Id operator()(VideoCommon::Shader::ExprOr& expr) {
1659 const Id type_def = decomp.GetTypeDefinition(Type::Bool);
1660 const Id op1 = Visit(expr.operand1);
1661 const Id op2 = Visit(expr.operand2);
1662 return decomp.Emit(decomp.OpLogicalOr(type_def, op1, op2));
1663 }
1664
1665 Id operator()(VideoCommon::Shader::ExprNot& expr) {
1666 const Id type_def = decomp.GetTypeDefinition(Type::Bool);
1667 const Id op1 = Visit(expr.operand1);
1668 return decomp.Emit(decomp.OpLogicalNot(type_def, op1));
1669 }
1670
1671 Id operator()(VideoCommon::Shader::ExprPredicate& expr) {
1672 const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
1673 return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.predicates.at(pred)));
1674 }
1675
1676 Id operator()(VideoCommon::Shader::ExprCondCode& expr) {
1677 const Node cc = decomp.ir.GetConditionCode(expr.cc);
1678 Id target;
1679
1680 if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
1681 const auto index = pred->GetIndex();
1682 switch (index) {
1683 case Tegra::Shader::Pred::NeverExecute:
1684 target = decomp.v_false;
1685 case Tegra::Shader::Pred::UnusedIndex:
1686 target = decomp.v_true;
1687 default:
1688 target = decomp.predicates.at(index);
1689 }
1690 } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
1691 target = decomp.internal_flags.at(static_cast<u32>(flag->GetFlag()));
1692 }
1693 return decomp.Emit(decomp.OpLoad(decomp.t_bool, target));
1694 }
1695
1696 Id operator()(VideoCommon::Shader::ExprVar& expr) {
1697 return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.flow_variables.at(expr.var_index)));
1698 }
1699
1700 Id operator()(VideoCommon::Shader::ExprBoolean& expr) {
1701 return expr.value ? decomp.v_true : decomp.v_false;
1702 }
1703
1704 Id Visit(VideoCommon::Shader::Expr& node) {
1705 return std::visit(*this, *node);
1706 }
1707
1708private:
1709 SPIRVDecompiler& decomp;
1710};
1711
1712class ASTDecompiler {
1713public:
1714 explicit ASTDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
1715
1716 void operator()(VideoCommon::Shader::ASTProgram& ast) {
1717 ASTNode current = ast.nodes.GetFirst();
1718 while (current) {
1719 Visit(current);
1720 current = current->GetNext();
1721 }
1722 }
1723
1724 void operator()(VideoCommon::Shader::ASTIfThen& ast) {
1725 ExprDecompiler expr_parser{decomp};
1726 const Id condition = expr_parser.Visit(ast.condition);
1727 const Id then_label = decomp.OpLabel();
1728 const Id endif_label = decomp.OpLabel();
1729 decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
1730 decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
1731 decomp.Emit(then_label);
1732 ASTNode current = ast.nodes.GetFirst();
1733 while (current) {
1734 Visit(current);
1735 current = current->GetNext();
1736 }
1737 decomp.Emit(decomp.OpBranch(endif_label));
1738 decomp.Emit(endif_label);
1739 }
1740
1741 void operator()(VideoCommon::Shader::ASTIfElse& ast) {
1742 UNREACHABLE();
1743 }
1744
1745 void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
1746 UNREACHABLE();
1747 }
1748
1749 void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
1750 decomp.VisitBasicBlock(ast.nodes);
1751 }
1752
1753 void operator()(VideoCommon::Shader::ASTVarSet& ast) {
1754 ExprDecompiler expr_parser{decomp};
1755 const Id condition = expr_parser.Visit(ast.condition);
1756 decomp.Emit(decomp.OpStore(decomp.flow_variables.at(ast.index), condition));
1757 }
1758
1759 void operator()(VideoCommon::Shader::ASTLabel& ast) {
1760 // Do nothing
1761 }
1762
1763 void operator()(VideoCommon::Shader::ASTGoto& ast) {
1764 UNREACHABLE();
1765 }
1766
1767 void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
1768 const Id loop_label = decomp.OpLabel();
1769 const Id endloop_label = decomp.OpLabel();
1770 const Id loop_start_block = decomp.OpLabel();
1771 const Id loop_end_block = decomp.OpLabel();
1772 current_loop_exit = endloop_label;
1773 decomp.Emit(decomp.OpBranch(loop_label));
1774 decomp.Emit(loop_label);
1775 decomp.Emit(
1776 decomp.OpLoopMerge(endloop_label, loop_end_block, spv::LoopControlMask::MaskNone));
1777 decomp.Emit(decomp.OpBranch(loop_start_block));
1778 decomp.Emit(loop_start_block);
1779 ASTNode current = ast.nodes.GetFirst();
1780 while (current) {
1781 Visit(current);
1782 current = current->GetNext();
1783 }
1784 ExprDecompiler expr_parser{decomp};
1785 const Id condition = expr_parser.Visit(ast.condition);
1786 decomp.Emit(decomp.OpBranchConditional(condition, loop_label, endloop_label));
1787 decomp.Emit(endloop_label);
1788 }
1789
1790 void operator()(VideoCommon::Shader::ASTReturn& ast) {
1791 if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
1792 ExprDecompiler expr_parser{decomp};
1793 const Id condition = expr_parser.Visit(ast.condition);
1794 const Id then_label = decomp.OpLabel();
1795 const Id endif_label = decomp.OpLabel();
1796 decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
1797 decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
1798 decomp.Emit(then_label);
1799 if (ast.kills) {
1800 decomp.Emit(decomp.OpKill());
1801 } else {
1802 decomp.PreExit();
1803 decomp.Emit(decomp.OpReturn());
1804 }
1805 decomp.Emit(endif_label);
1806 } else {
1807 const Id next_block = decomp.OpLabel();
1808 decomp.Emit(decomp.OpBranch(next_block));
1809 decomp.Emit(next_block);
1810 if (ast.kills) {
1811 decomp.Emit(decomp.OpKill());
1812 } else {
1813 decomp.PreExit();
1814 decomp.Emit(decomp.OpReturn());
1815 }
1816 decomp.Emit(decomp.OpLabel());
1817 }
1818 }
1819
1820 void operator()(VideoCommon::Shader::ASTBreak& ast) {
1821 if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
1822 ExprDecompiler expr_parser{decomp};
1823 const Id condition = expr_parser.Visit(ast.condition);
1824 const Id then_label = decomp.OpLabel();
1825 const Id endif_label = decomp.OpLabel();
1826 decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
1827 decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
1828 decomp.Emit(then_label);
1829 decomp.Emit(decomp.OpBranch(current_loop_exit));
1830 decomp.Emit(endif_label);
1831 } else {
1832 const Id next_block = decomp.OpLabel();
1833 decomp.Emit(decomp.OpBranch(next_block));
1834 decomp.Emit(next_block);
1835 decomp.Emit(decomp.OpBranch(current_loop_exit));
1836 decomp.Emit(decomp.OpLabel());
1837 }
1838 }
1839
1840 void Visit(VideoCommon::Shader::ASTNode& node) {
1841 std::visit(*this, *node->GetInnerData());
1842 }
1843
1844private:
1845 SPIRVDecompiler& decomp;
1846 Id current_loop_exit{};
1847};
1848
1849void SPIRVDecompiler::DecompileAST() {
1850 const u32 num_flow_variables = ir.GetASTNumVariables();
1851 for (u32 i = 0; i < num_flow_variables; i++) {
1852 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
1853 Name(id, fmt::format("flow_var_{}", i));
1854 flow_variables.emplace(i, AddGlobalVariable(id));
1855 }
1856 ASTDecompiler decompiler{*this};
1857 VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
1858 decompiler.Visit(program);
1859 const Id next_block = OpLabel();
1860 Emit(OpBranch(next_block));
1861 Emit(next_block);
1862}
1863
1588DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir, 1864DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
1589 Maxwell::ShaderStage stage) { 1865 Maxwell::ShaderStage stage) {
1590 auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage); 1866 auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage);
diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp
new file mode 100644
index 000000000..436d45f4b
--- /dev/null
+++ b/src/video_core/shader/ast.cpp
@@ -0,0 +1,738 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <string>
6
7#include "common/assert.h"
8#include "common/common_types.h"
9#include "video_core/shader/ast.h"
10#include "video_core/shader/expr.h"
11
12namespace VideoCommon::Shader {
13
14ASTZipper::ASTZipper() = default;
15
16void ASTZipper::Init(const ASTNode new_first, const ASTNode parent) {
17 ASSERT(new_first->manager == nullptr);
18 first = new_first;
19 last = new_first;
20
21 ASTNode current = first;
22 while (current) {
23 current->manager = this;
24 current->parent = parent;
25 last = current;
26 current = current->next;
27 }
28}
29
30void ASTZipper::PushBack(const ASTNode new_node) {
31 ASSERT(new_node->manager == nullptr);
32 new_node->previous = last;
33 if (last) {
34 last->next = new_node;
35 }
36 new_node->next.reset();
37 last = new_node;
38 if (!first) {
39 first = new_node;
40 }
41 new_node->manager = this;
42}
43
44void ASTZipper::PushFront(const ASTNode new_node) {
45 ASSERT(new_node->manager == nullptr);
46 new_node->previous.reset();
47 new_node->next = first;
48 if (first) {
49 first->previous = new_node;
50 }
51 if (last == first) {
52 last = new_node;
53 }
54 first = new_node;
55 new_node->manager = this;
56}
57
58void ASTZipper::InsertAfter(const ASTNode new_node, const ASTNode at_node) {
59 ASSERT(new_node->manager == nullptr);
60 if (!at_node) {
61 PushFront(new_node);
62 return;
63 }
64 const ASTNode next = at_node->next;
65 if (next) {
66 next->previous = new_node;
67 }
68 new_node->previous = at_node;
69 if (at_node == last) {
70 last = new_node;
71 }
72 new_node->next = next;
73 at_node->next = new_node;
74 new_node->manager = this;
75}
76
77void ASTZipper::InsertBefore(const ASTNode new_node, const ASTNode at_node) {
78 ASSERT(new_node->manager == nullptr);
79 if (!at_node) {
80 PushBack(new_node);
81 return;
82 }
83 const ASTNode previous = at_node->previous;
84 if (previous) {
85 previous->next = new_node;
86 }
87 new_node->next = at_node;
88 if (at_node == first) {
89 first = new_node;
90 }
91 new_node->previous = previous;
92 at_node->previous = new_node;
93 new_node->manager = this;
94}
95
96void ASTZipper::DetachTail(ASTNode node) {
97 ASSERT(node->manager == this);
98 if (node == first) {
99 first.reset();
100 last.reset();
101 return;
102 }
103
104 last = node->previous;
105 last->next.reset();
106 node->previous.reset();
107
108 ASTNode current = std::move(node);
109 while (current) {
110 current->manager = nullptr;
111 current->parent.reset();
112 current = current->next;
113 }
114}
115
116void ASTZipper::DetachSegment(const ASTNode start, const ASTNode end) {
117 ASSERT(start->manager == this && end->manager == this);
118 if (start == end) {
119 DetachSingle(start);
120 return;
121 }
122 const ASTNode prev = start->previous;
123 const ASTNode post = end->next;
124 if (!prev) {
125 first = post;
126 } else {
127 prev->next = post;
128 }
129 if (!post) {
130 last = prev;
131 } else {
132 post->previous = prev;
133 }
134 start->previous.reset();
135 end->next.reset();
136 ASTNode current = start;
137 bool found = false;
138 while (current) {
139 current->manager = nullptr;
140 current->parent.reset();
141 found |= current == end;
142 current = current->next;
143 }
144 ASSERT(found);
145}
146
147void ASTZipper::DetachSingle(const ASTNode node) {
148 ASSERT(node->manager == this);
149 const ASTNode prev = node->previous;
150 const ASTNode post = node->next;
151 node->previous.reset();
152 node->next.reset();
153 if (!prev) {
154 first = post;
155 } else {
156 prev->next = post;
157 }
158 if (!post) {
159 last = prev;
160 } else {
161 post->previous = prev;
162 }
163
164 node->manager = nullptr;
165 node->parent.reset();
166}
167
168void ASTZipper::Remove(const ASTNode node) {
169 ASSERT(node->manager == this);
170 const ASTNode next = node->next;
171 const ASTNode previous = node->previous;
172 if (previous) {
173 previous->next = next;
174 }
175 if (next) {
176 next->previous = previous;
177 }
178 node->parent.reset();
179 node->manager = nullptr;
180 if (node == last) {
181 last = previous;
182 }
183 if (node == first) {
184 first = next;
185 }
186}
187
188class ExprPrinter final {
189public:
190 void operator()(const ExprAnd& expr) {
191 inner += "( ";
192 std::visit(*this, *expr.operand1);
193 inner += " && ";
194 std::visit(*this, *expr.operand2);
195 inner += ')';
196 }
197
198 void operator()(const ExprOr& expr) {
199 inner += "( ";
200 std::visit(*this, *expr.operand1);
201 inner += " || ";
202 std::visit(*this, *expr.operand2);
203 inner += ')';
204 }
205
206 void operator()(const ExprNot& expr) {
207 inner += "!";
208 std::visit(*this, *expr.operand1);
209 }
210
211 void operator()(const ExprPredicate& expr) {
212 inner += "P" + std::to_string(expr.predicate);
213 }
214
215 void operator()(const ExprCondCode& expr) {
216 u32 cc = static_cast<u32>(expr.cc);
217 inner += "CC" + std::to_string(cc);
218 }
219
220 void operator()(const ExprVar& expr) {
221 inner += "V" + std::to_string(expr.var_index);
222 }
223
224 void operator()(const ExprBoolean& expr) {
225 inner += expr.value ? "true" : "false";
226 }
227
228 const std::string& GetResult() const {
229 return inner;
230 }
231
232 std::string inner{};
233};
234
235class ASTPrinter {
236public:
237 void operator()(const ASTProgram& ast) {
238 scope++;
239 inner += "program {\n";
240 ASTNode current = ast.nodes.GetFirst();
241 while (current) {
242 Visit(current);
243 current = current->GetNext();
244 }
245 inner += "}\n";
246 scope--;
247 }
248
249 void operator()(const ASTIfThen& ast) {
250 ExprPrinter expr_parser{};
251 std::visit(expr_parser, *ast.condition);
252 inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
253 scope++;
254 ASTNode current = ast.nodes.GetFirst();
255 while (current) {
256 Visit(current);
257 current = current->GetNext();
258 }
259 scope--;
260 inner += Ident() + "}\n";
261 }
262
263 void operator()(const ASTIfElse& ast) {
264 inner += Ident() + "else {\n";
265 scope++;
266 ASTNode current = ast.nodes.GetFirst();
267 while (current) {
268 Visit(current);
269 current = current->GetNext();
270 }
271 scope--;
272 inner += Ident() + "}\n";
273 }
274
275 void operator()(const ASTBlockEncoded& ast) {
276 inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
277 ");\n";
278 }
279
280 void operator()(const ASTBlockDecoded& ast) {
281 inner += Ident() + "Block;\n";
282 }
283
284 void operator()(const ASTVarSet& ast) {
285 ExprPrinter expr_parser{};
286 std::visit(expr_parser, *ast.condition);
287 inner +=
288 Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
289 }
290
291 void operator()(const ASTLabel& ast) {
292 inner += "Label_" + std::to_string(ast.index) + ":\n";
293 }
294
295 void operator()(const ASTGoto& ast) {
296 ExprPrinter expr_parser{};
297 std::visit(expr_parser, *ast.condition);
298 inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
299 std::to_string(ast.label) + ";\n";
300 }
301
302 void operator()(const ASTDoWhile& ast) {
303 ExprPrinter expr_parser{};
304 std::visit(expr_parser, *ast.condition);
305 inner += Ident() + "do {\n";
306 scope++;
307 ASTNode current = ast.nodes.GetFirst();
308 while (current) {
309 Visit(current);
310 current = current->GetNext();
311 }
312 scope--;
313 inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
314 }
315
316 void operator()(const ASTReturn& ast) {
317 ExprPrinter expr_parser{};
318 std::visit(expr_parser, *ast.condition);
319 inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
320 (ast.kills ? "discard" : "exit") + ";\n";
321 }
322
323 void operator()(const ASTBreak& ast) {
324 ExprPrinter expr_parser{};
325 std::visit(expr_parser, *ast.condition);
326 inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
327 }
328
329 std::string& Ident() {
330 if (memo_scope == scope) {
331 return tabs_memo;
332 }
333 tabs_memo = tabs.substr(0, scope * 2);
334 memo_scope = scope;
335 return tabs_memo;
336 }
337
338 void Visit(ASTNode& node) {
339 std::visit(*this, *node->GetInnerData());
340 }
341
342 const std::string& GetResult() const {
343 return inner;
344 }
345
346private:
347 std::string inner{};
348 u32 scope{};
349
350 std::string tabs_memo{};
351 u32 memo_scope{};
352
353 static constexpr std::string_view tabs{" "};
354};
355
356std::string ASTManager::Print() {
357 ASTPrinter printer{};
358 printer.Visit(main_node);
359 return printer.GetResult();
360}
361
362ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation)
363 : full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {};
364
365ASTManager::~ASTManager() {
366 Clear();
367}
368
369void ASTManager::Init() {
370 main_node = ASTBase::Make<ASTProgram>(ASTNode{});
371 program = std::get_if<ASTProgram>(main_node->GetInnerData());
372 false_condition = MakeExpr<ExprBoolean>(false);
373}
374
375void ASTManager::DeclareLabel(u32 address) {
376 const auto pair = labels_map.emplace(address, labels_count);
377 if (pair.second) {
378 labels_count++;
379 labels.resize(labels_count);
380 }
381}
382
383void ASTManager::InsertLabel(u32 address) {
384 const u32 index = labels_map[address];
385 const ASTNode label = ASTBase::Make<ASTLabel>(main_node, index);
386 labels[index] = label;
387 program->nodes.PushBack(label);
388}
389
390void ASTManager::InsertGoto(Expr condition, u32 address) {
391 const u32 index = labels_map[address];
392 const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, std::move(condition), index);
393 gotos.push_back(goto_node);
394 program->nodes.PushBack(goto_node);
395}
396
397void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
398 ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
399 program->nodes.PushBack(std::move(block));
400}
401
402void ASTManager::InsertReturn(Expr condition, bool kills) {
403 ASTNode node = ASTBase::Make<ASTReturn>(main_node, std::move(condition), kills);
404 program->nodes.PushBack(std::move(node));
405}
406
407// The decompile algorithm is based on
408// "Taming control flow: A structured approach to eliminating goto statements"
409// by AM Erosa, LJ Hendren 1994. In general, the idea is to get gotos to be
410// on the same structured level as the label which they jump to. This is done,
411// through outward/inward movements and lifting. Once they are at the same
412// level, you can enclose them in an "if" structure or a "do-while" structure.
413void ASTManager::Decompile() {
414 auto it = gotos.begin();
415 while (it != gotos.end()) {
416 const ASTNode goto_node = *it;
417 const auto label_index = goto_node->GetGotoLabel();
418 if (!label_index) {
419 return;
420 }
421 const ASTNode label = labels[*label_index];
422 if (!full_decompile) {
423 // We only decompile backward jumps
424 if (!IsBackwardsJump(goto_node, label)) {
425 it++;
426 continue;
427 }
428 }
429 if (IndirectlyRelated(goto_node, label)) {
430 while (!DirectlyRelated(goto_node, label)) {
431 MoveOutward(goto_node);
432 }
433 }
434 if (DirectlyRelated(goto_node, label)) {
435 u32 goto_level = goto_node->GetLevel();
436 const u32 label_level = label->GetLevel();
437 while (label_level < goto_level) {
438 MoveOutward(goto_node);
439 goto_level--;
440 }
441 // TODO(Blinkhawk): Implement Lifting and Inward Movements
442 }
443 if (label->GetParent() == goto_node->GetParent()) {
444 bool is_loop = false;
445 ASTNode current = goto_node->GetPrevious();
446 while (current) {
447 if (current == label) {
448 is_loop = true;
449 break;
450 }
451 current = current->GetPrevious();
452 }
453
454 if (is_loop) {
455 EncloseDoWhile(goto_node, label);
456 } else {
457 EncloseIfThen(goto_node, label);
458 }
459 it = gotos.erase(it);
460 continue;
461 }
462 it++;
463 }
464 if (full_decompile) {
465 for (const ASTNode& label : labels) {
466 auto& manager = label->GetManager();
467 manager.Remove(label);
468 }
469 labels.clear();
470 } else {
471 auto label_it = labels.begin();
472 while (label_it != labels.end()) {
473 bool can_remove = true;
474 ASTNode label = *label_it;
475 for (const ASTNode& goto_node : gotos) {
476 const auto label_index = goto_node->GetGotoLabel();
477 if (!label_index) {
478 return;
479 }
480 ASTNode& glabel = labels[*label_index];
481 if (glabel == label) {
482 can_remove = false;
483 break;
484 }
485 }
486 if (can_remove) {
487 label->MarkLabelUnused();
488 }
489 }
490 }
491}
492
493bool ASTManager::IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const {
494 u32 goto_level = goto_node->GetLevel();
495 u32 label_level = label_node->GetLevel();
496 while (goto_level > label_level) {
497 goto_level--;
498 goto_node = goto_node->GetParent();
499 }
500 while (label_level > goto_level) {
501 label_level--;
502 label_node = label_node->GetParent();
503 }
504 while (goto_node->GetParent() != label_node->GetParent()) {
505 goto_node = goto_node->GetParent();
506 label_node = label_node->GetParent();
507 }
508 ASTNode current = goto_node->GetPrevious();
509 while (current) {
510 if (current == label_node) {
511 return true;
512 }
513 current = current->GetPrevious();
514 }
515 return false;
516}
517
518bool ASTManager::IndirectlyRelated(const ASTNode& first, const ASTNode& second) const {
519 return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
520}
521
522bool ASTManager::DirectlyRelated(const ASTNode& first, const ASTNode& second) const {
523 if (first->GetParent() == second->GetParent()) {
524 return false;
525 }
526 const u32 first_level = first->GetLevel();
527 const u32 second_level = second->GetLevel();
528 u32 min_level;
529 u32 max_level;
530 ASTNode max;
531 ASTNode min;
532 if (first_level > second_level) {
533 min_level = second_level;
534 min = second;
535 max_level = first_level;
536 max = first;
537 } else {
538 min_level = first_level;
539 min = first;
540 max_level = second_level;
541 max = second;
542 }
543
544 while (max_level > min_level) {
545 max_level--;
546 max = max->GetParent();
547 }
548
549 return min->GetParent() == max->GetParent();
550}
551
552void ASTManager::ShowCurrentState(std::string_view state) {
553 LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
554 SanityCheck();
555}
556
557void ASTManager::SanityCheck() {
558 for (auto& label : labels) {
559 if (!label->GetParent()) {
560 LOG_CRITICAL(HW_GPU, "Sanity Check Failed");
561 }
562 }
563}
564
565void ASTManager::EncloseDoWhile(ASTNode goto_node, ASTNode label) {
566 ASTZipper& zipper = goto_node->GetManager();
567 const ASTNode loop_start = label->GetNext();
568 if (loop_start == goto_node) {
569 zipper.Remove(goto_node);
570 return;
571 }
572 const ASTNode parent = label->GetParent();
573 const Expr condition = goto_node->GetGotoCondition();
574 zipper.DetachSegment(loop_start, goto_node);
575 const ASTNode do_while_node = ASTBase::Make<ASTDoWhile>(parent, condition);
576 ASTZipper* sub_zipper = do_while_node->GetSubNodes();
577 sub_zipper->Init(loop_start, do_while_node);
578 zipper.InsertAfter(do_while_node, label);
579 sub_zipper->Remove(goto_node);
580}
581
582void ASTManager::EncloseIfThen(ASTNode goto_node, ASTNode label) {
583 ASTZipper& zipper = goto_node->GetManager();
584 const ASTNode if_end = label->GetPrevious();
585 if (if_end == goto_node) {
586 zipper.Remove(goto_node);
587 return;
588 }
589 const ASTNode prev = goto_node->GetPrevious();
590 const Expr condition = goto_node->GetGotoCondition();
591 bool do_else = false;
592 if (!disable_else_derivation && prev->IsIfThen()) {
593 const Expr if_condition = prev->GetIfCondition();
594 do_else = ExprAreEqual(if_condition, condition);
595 }
596 const ASTNode parent = label->GetParent();
597 zipper.DetachSegment(goto_node, if_end);
598 ASTNode if_node;
599 if (do_else) {
600 if_node = ASTBase::Make<ASTIfElse>(parent);
601 } else {
602 Expr neg_condition = MakeExprNot(condition);
603 if_node = ASTBase::Make<ASTIfThen>(parent, neg_condition);
604 }
605 ASTZipper* sub_zipper = if_node->GetSubNodes();
606 sub_zipper->Init(goto_node, if_node);
607 zipper.InsertAfter(if_node, prev);
608 sub_zipper->Remove(goto_node);
609}
610
611void ASTManager::MoveOutward(ASTNode goto_node) {
612 ASTZipper& zipper = goto_node->GetManager();
613 const ASTNode parent = goto_node->GetParent();
614 ASTZipper& zipper2 = parent->GetManager();
615 const ASTNode grandpa = parent->GetParent();
616 const bool is_loop = parent->IsLoop();
617 const bool is_else = parent->IsIfElse();
618 const bool is_if = parent->IsIfThen();
619
620 const ASTNode prev = goto_node->GetPrevious();
621 const ASTNode post = goto_node->GetNext();
622
623 const Expr condition = goto_node->GetGotoCondition();
624 zipper.DetachSingle(goto_node);
625 if (is_loop) {
626 const u32 var_index = NewVariable();
627 const Expr var_condition = MakeExpr<ExprVar>(var_index);
628 const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
629 const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
630 zipper2.InsertBefore(var_node_init, parent);
631 zipper.InsertAfter(var_node, prev);
632 goto_node->SetGotoCondition(var_condition);
633 const ASTNode break_node = ASTBase::Make<ASTBreak>(parent, var_condition);
634 zipper.InsertAfter(break_node, var_node);
635 } else if (is_if || is_else) {
636 const u32 var_index = NewVariable();
637 const Expr var_condition = MakeExpr<ExprVar>(var_index);
638 const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
639 const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
640 if (is_if) {
641 zipper2.InsertBefore(var_node_init, parent);
642 } else {
643 zipper2.InsertBefore(var_node_init, parent->GetPrevious());
644 }
645 zipper.InsertAfter(var_node, prev);
646 goto_node->SetGotoCondition(var_condition);
647 if (post) {
648 zipper.DetachTail(post);
649 const ASTNode if_node = ASTBase::Make<ASTIfThen>(parent, MakeExprNot(var_condition));
650 ASTZipper* sub_zipper = if_node->GetSubNodes();
651 sub_zipper->Init(post, if_node);
652 zipper.InsertAfter(if_node, var_node);
653 }
654 } else {
655 UNREACHABLE();
656 }
657 const ASTNode next = parent->GetNext();
658 if (is_if && next && next->IsIfElse()) {
659 zipper2.InsertAfter(goto_node, next);
660 goto_node->SetParent(grandpa);
661 return;
662 }
663 zipper2.InsertAfter(goto_node, parent);
664 goto_node->SetParent(grandpa);
665}
666
667class ASTClearer {
668public:
669 ASTClearer() = default;
670
671 void operator()(const ASTProgram& ast) {
672 ASTNode current = ast.nodes.GetFirst();
673 while (current) {
674 Visit(current);
675 current = current->GetNext();
676 }
677 }
678
679 void operator()(const ASTIfThen& ast) {
680 ASTNode current = ast.nodes.GetFirst();
681 while (current) {
682 Visit(current);
683 current = current->GetNext();
684 }
685 }
686
687 void operator()(const ASTIfElse& ast) {
688 ASTNode current = ast.nodes.GetFirst();
689 while (current) {
690 Visit(current);
691 current = current->GetNext();
692 }
693 }
694
695 void operator()([[maybe_unused]] const ASTBlockEncoded& ast) {}
696
697 void operator()(ASTBlockDecoded& ast) {
698 ast.nodes.clear();
699 }
700
701 void operator()([[maybe_unused]] const ASTVarSet& ast) {}
702
703 void operator()([[maybe_unused]] const ASTLabel& ast) {}
704
705 void operator()([[maybe_unused]] const ASTGoto& ast) {}
706
707 void operator()(const ASTDoWhile& ast) {
708 ASTNode current = ast.nodes.GetFirst();
709 while (current) {
710 Visit(current);
711 current = current->GetNext();
712 }
713 }
714
715 void operator()([[maybe_unused]] const ASTReturn& ast) {}
716
717 void operator()([[maybe_unused]] const ASTBreak& ast) {}
718
719 void Visit(const ASTNode& node) {
720 std::visit(*this, *node->GetInnerData());
721 node->Clear();
722 }
723};
724
725void ASTManager::Clear() {
726 if (!main_node) {
727 return;
728 }
729 ASTClearer clearer{};
730 clearer.Visit(main_node);
731 main_node.reset();
732 program = nullptr;
733 labels_map.clear();
734 labels.clear();
735 gotos.clear();
736}
737
738} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
new file mode 100644
index 000000000..d7bf11821
--- /dev/null
+++ b/src/video_core/shader/ast.h
@@ -0,0 +1,400 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <list>
9#include <memory>
10#include <optional>
11#include <string>
12#include <unordered_map>
13#include <vector>
14
15#include "video_core/shader/expr.h"
16#include "video_core/shader/node.h"
17
18namespace VideoCommon::Shader {
19
20class ASTBase;
21class ASTBlockDecoded;
22class ASTBlockEncoded;
23class ASTBreak;
24class ASTDoWhile;
25class ASTGoto;
26class ASTIfElse;
27class ASTIfThen;
28class ASTLabel;
29class ASTProgram;
30class ASTReturn;
31class ASTVarSet;
32
33using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
34 ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
35
36using ASTNode = std::shared_ptr<ASTBase>;
37
38enum class ASTZipperType : u32 {
39 Program,
40 IfThen,
41 IfElse,
42 Loop,
43};
44
45class ASTZipper final {
46public:
47 explicit ASTZipper();
48
49 void Init(ASTNode first, ASTNode parent);
50
51 ASTNode GetFirst() const {
52 return first;
53 }
54
55 ASTNode GetLast() const {
56 return last;
57 }
58
59 void PushBack(ASTNode new_node);
60 void PushFront(ASTNode new_node);
61 void InsertAfter(ASTNode new_node, ASTNode at_node);
62 void InsertBefore(ASTNode new_node, ASTNode at_node);
63 void DetachTail(ASTNode node);
64 void DetachSingle(ASTNode node);
65 void DetachSegment(ASTNode start, ASTNode end);
66 void Remove(ASTNode node);
67
68 ASTNode first{};
69 ASTNode last{};
70};
71
72class ASTProgram {
73public:
74 ASTZipper nodes{};
75};
76
77class ASTIfThen {
78public:
79 explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {}
80 Expr condition;
81 ASTZipper nodes{};
82};
83
84class ASTIfElse {
85public:
86 ASTZipper nodes{};
87};
88
89class ASTBlockEncoded {
90public:
91 explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
92 u32 start;
93 u32 end;
94};
95
96class ASTBlockDecoded {
97public:
98 explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {}
99 NodeBlock nodes;
100};
101
102class ASTVarSet {
103public:
104 explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {}
105 u32 index;
106 Expr condition;
107};
108
109class ASTLabel {
110public:
111 explicit ASTLabel(u32 index) : index{index} {}
112 u32 index;
113 bool unused{};
114};
115
116class ASTGoto {
117public:
118 explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {}
119 Expr condition;
120 u32 label;
121};
122
123class ASTDoWhile {
124public:
125 explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {}
126 Expr condition;
127 ASTZipper nodes{};
128};
129
130class ASTReturn {
131public:
132 explicit ASTReturn(Expr condition, bool kills)
133 : condition{std::move(condition)}, kills{kills} {}
134 Expr condition;
135 bool kills;
136};
137
138class ASTBreak {
139public:
140 explicit ASTBreak(Expr condition) : condition{std::move(condition)} {}
141 Expr condition;
142};
143
144class ASTBase {
145public:
146 explicit ASTBase(ASTNode parent, ASTData data)
147 : data{std::move(data)}, parent{std::move(parent)} {}
148
149 template <class U, class... Args>
150 static ASTNode Make(ASTNode parent, Args&&... args) {
151 return std::make_shared<ASTBase>(std::move(parent),
152 ASTData(U(std::forward<Args>(args)...)));
153 }
154
155 void SetParent(ASTNode new_parent) {
156 parent = std::move(new_parent);
157 }
158
159 ASTNode& GetParent() {
160 return parent;
161 }
162
163 const ASTNode& GetParent() const {
164 return parent;
165 }
166
167 u32 GetLevel() const {
168 u32 level = 0;
169 auto next_parent = parent;
170 while (next_parent) {
171 next_parent = next_parent->GetParent();
172 level++;
173 }
174 return level;
175 }
176
177 ASTData* GetInnerData() {
178 return &data;
179 }
180
181 const ASTData* GetInnerData() const {
182 return &data;
183 }
184
185 ASTNode GetNext() const {
186 return next;
187 }
188
189 ASTNode GetPrevious() const {
190 return previous;
191 }
192
193 ASTZipper& GetManager() {
194 return *manager;
195 }
196
197 const ASTZipper& GetManager() const {
198 return *manager;
199 }
200
201 std::optional<u32> GetGotoLabel() const {
202 auto inner = std::get_if<ASTGoto>(&data);
203 if (inner) {
204 return {inner->label};
205 }
206 return {};
207 }
208
209 Expr GetGotoCondition() const {
210 auto inner = std::get_if<ASTGoto>(&data);
211 if (inner) {
212 return inner->condition;
213 }
214 return nullptr;
215 }
216
217 void MarkLabelUnused() {
218 auto inner = std::get_if<ASTLabel>(&data);
219 if (inner) {
220 inner->unused = true;
221 }
222 }
223
224 bool IsLabelUnused() const {
225 auto inner = std::get_if<ASTLabel>(&data);
226 if (inner) {
227 return inner->unused;
228 }
229 return true;
230 }
231
232 std::optional<u32> GetLabelIndex() const {
233 auto inner = std::get_if<ASTLabel>(&data);
234 if (inner) {
235 return {inner->index};
236 }
237 return {};
238 }
239
240 Expr GetIfCondition() const {
241 auto inner = std::get_if<ASTIfThen>(&data);
242 if (inner) {
243 return inner->condition;
244 }
245 return nullptr;
246 }
247
248 void SetGotoCondition(Expr new_condition) {
249 auto inner = std::get_if<ASTGoto>(&data);
250 if (inner) {
251 inner->condition = std::move(new_condition);
252 }
253 }
254
255 bool IsIfThen() const {
256 return std::holds_alternative<ASTIfThen>(data);
257 }
258
259 bool IsIfElse() const {
260 return std::holds_alternative<ASTIfElse>(data);
261 }
262
263 bool IsBlockEncoded() const {
264 return std::holds_alternative<ASTBlockEncoded>(data);
265 }
266
267 void TransformBlockEncoded(NodeBlock&& nodes) {
268 data = ASTBlockDecoded(std::move(nodes));
269 }
270
271 bool IsLoop() const {
272 return std::holds_alternative<ASTDoWhile>(data);
273 }
274
275 ASTZipper* GetSubNodes() {
276 if (std::holds_alternative<ASTProgram>(data)) {
277 return &std::get_if<ASTProgram>(&data)->nodes;
278 }
279 if (std::holds_alternative<ASTIfThen>(data)) {
280 return &std::get_if<ASTIfThen>(&data)->nodes;
281 }
282 if (std::holds_alternative<ASTIfElse>(data)) {
283 return &std::get_if<ASTIfElse>(&data)->nodes;
284 }
285 if (std::holds_alternative<ASTDoWhile>(data)) {
286 return &std::get_if<ASTDoWhile>(&data)->nodes;
287 }
288 return nullptr;
289 }
290
291 void Clear() {
292 next.reset();
293 previous.reset();
294 parent.reset();
295 manager = nullptr;
296 }
297
298private:
299 friend class ASTZipper;
300
301 ASTData data;
302 ASTNode parent{};
303 ASTNode next{};
304 ASTNode previous{};
305 ASTZipper* manager{};
306};
307
308class ASTManager final {
309public:
310 ASTManager(bool full_decompile, bool disable_else_derivation);
311 ~ASTManager();
312
313 ASTManager(const ASTManager& o) = delete;
314 ASTManager& operator=(const ASTManager& other) = delete;
315
316 ASTManager(ASTManager&& other) noexcept = default;
317 ASTManager& operator=(ASTManager&& other) noexcept = default;
318
319 void Init();
320
321 void DeclareLabel(u32 address);
322
323 void InsertLabel(u32 address);
324
325 void InsertGoto(Expr condition, u32 address);
326
327 void InsertBlock(u32 start_address, u32 end_address);
328
329 void InsertReturn(Expr condition, bool kills);
330
331 std::string Print();
332
333 void Decompile();
334
335 void ShowCurrentState(std::string_view state);
336
337 void SanityCheck();
338
339 void Clear();
340
341 bool IsFullyDecompiled() const {
342 if (full_decompile) {
343 return gotos.empty();
344 }
345
346 for (ASTNode goto_node : gotos) {
347 auto label_index = goto_node->GetGotoLabel();
348 if (!label_index) {
349 return false;
350 }
351 ASTNode glabel = labels[*label_index];
352 if (IsBackwardsJump(goto_node, glabel)) {
353 return false;
354 }
355 }
356 return true;
357 }
358
359 ASTNode GetProgram() const {
360 return main_node;
361 }
362
363 u32 GetVariables() const {
364 return variables;
365 }
366
367 const std::vector<ASTNode>& GetLabels() const {
368 return labels;
369 }
370
371private:
372 bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
373
374 bool IndirectlyRelated(const ASTNode& first, const ASTNode& second) const;
375
376 bool DirectlyRelated(const ASTNode& first, const ASTNode& second) const;
377
378 void EncloseDoWhile(ASTNode goto_node, ASTNode label);
379
380 void EncloseIfThen(ASTNode goto_node, ASTNode label);
381
382 void MoveOutward(ASTNode goto_node);
383
384 u32 NewVariable() {
385 return variables++;
386 }
387
388 bool full_decompile{};
389 bool disable_else_derivation{};
390 std::unordered_map<u32, u32> labels_map{};
391 u32 labels_count{};
392 std::vector<ASTNode> labels{};
393 std::list<ASTNode> gotos{};
394 u32 variables{};
395 ASTProgram* program{};
396 ASTNode main_node{};
397 Expr false_condition{};
398};
399
400} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/compiler_settings.cpp b/src/video_core/shader/compiler_settings.cpp
new file mode 100644
index 000000000..cddcbd4f0
--- /dev/null
+++ b/src/video_core/shader/compiler_settings.cpp
@@ -0,0 +1,26 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "video_core/shader/compiler_settings.h"
6
7namespace VideoCommon::Shader {
8
9std::string CompileDepthAsString(const CompileDepth cd) {
10 switch (cd) {
11 case CompileDepth::BruteForce:
12 return "Brute Force Compile";
13 case CompileDepth::FlowStack:
14 return "Simple Flow Stack Mode";
15 case CompileDepth::NoFlowStack:
16 return "Remove Flow Stack";
17 case CompileDepth::DecompileBackwards:
18 return "Decompile Backward Jumps";
19 case CompileDepth::FullDecompile:
20 return "Full Decompilation";
21 default:
22 return "Unknown Compiler Process";
23 }
24}
25
26} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/compiler_settings.h b/src/video_core/shader/compiler_settings.h
new file mode 100644
index 000000000..916018c01
--- /dev/null
+++ b/src/video_core/shader/compiler_settings.h
@@ -0,0 +1,26 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "video_core/engines/shader_bytecode.h"
8
9namespace VideoCommon::Shader {
10
11enum class CompileDepth : u32 {
12 BruteForce = 0,
13 FlowStack = 1,
14 NoFlowStack = 2,
15 DecompileBackwards = 3,
16 FullDecompile = 4,
17};
18
19std::string CompileDepthAsString(CompileDepth cd);
20
21struct CompilerSettings {
22 CompileDepth depth{CompileDepth::NoFlowStack};
23 bool disable_else_derivation{true};
24};
25
26} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index ec3a76690..268d1aed0 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -4,13 +4,14 @@
4 4
5#include <list> 5#include <list>
6#include <map> 6#include <map>
7#include <set>
7#include <stack> 8#include <stack>
8#include <unordered_map> 9#include <unordered_map>
9#include <unordered_set>
10#include <vector> 10#include <vector>
11 11
12#include "common/assert.h" 12#include "common/assert.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "video_core/shader/ast.h"
14#include "video_core/shader/control_flow.h" 15#include "video_core/shader/control_flow.h"
15#include "video_core/shader/shader_ir.h" 16#include "video_core/shader/shader_ir.h"
16 17
@@ -64,12 +65,13 @@ struct CFGRebuildState {
64 std::list<u32> inspect_queries{}; 65 std::list<u32> inspect_queries{};
65 std::list<Query> queries{}; 66 std::list<Query> queries{};
66 std::unordered_map<u32, u32> registered{}; 67 std::unordered_map<u32, u32> registered{};
67 std::unordered_set<u32> labels{}; 68 std::set<u32> labels{};
68 std::map<u32, u32> ssy_labels{}; 69 std::map<u32, u32> ssy_labels{};
69 std::map<u32, u32> pbk_labels{}; 70 std::map<u32, u32> pbk_labels{};
70 std::unordered_map<u32, BlockStack> stacks{}; 71 std::unordered_map<u32, BlockStack> stacks{};
71 const ProgramCode& program_code; 72 const ProgramCode& program_code;
72 const std::size_t program_size; 73 const std::size_t program_size;
74 ASTManager* manager;
73}; 75};
74 76
75enum class BlockCollision : u32 { None, Found, Inside }; 77enum class BlockCollision : u32 { None, Found, Inside };
@@ -415,38 +417,133 @@ bool TryQuery(CFGRebuildState& state) {
415} 417}
416} // Anonymous namespace 418} // Anonymous namespace
417 419
418std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, 420void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
419 std::size_t program_size, u32 start_address) { 421 const auto get_expr = ([&](const Condition& cond) -> Expr {
420 CFGRebuildState state{program_code, program_size, start_address}; 422 Expr result{};
423 if (cond.cc != ConditionCode::T) {
424 result = MakeExpr<ExprCondCode>(cond.cc);
425 }
426 if (cond.predicate != Pred::UnusedIndex) {
427 u32 pred = static_cast<u32>(cond.predicate);
428 bool negate = false;
429 if (pred > 7) {
430 negate = true;
431 pred -= 8;
432 }
433 Expr extra = MakeExpr<ExprPredicate>(pred);
434 if (negate) {
435 extra = MakeExpr<ExprNot>(extra);
436 }
437 if (result) {
438 return MakeExpr<ExprAnd>(extra, result);
439 }
440 return extra;
441 }
442 if (result) {
443 return result;
444 }
445 return MakeExpr<ExprBoolean>(true);
446 });
447 if (branch.address < 0) {
448 if (branch.kill) {
449 mm.InsertReturn(get_expr(branch.condition), true);
450 return;
451 }
452 mm.InsertReturn(get_expr(branch.condition), false);
453 return;
454 }
455 mm.InsertGoto(get_expr(branch.condition), branch.address);
456}
457
458void DecompileShader(CFGRebuildState& state) {
459 state.manager->Init();
460 for (auto label : state.labels) {
461 state.manager->DeclareLabel(label);
462 }
463 for (auto& block : state.block_info) {
464 if (state.labels.count(block.start) != 0) {
465 state.manager->InsertLabel(block.start);
466 }
467 u32 end = block.branch.ignore ? block.end + 1 : block.end;
468 state.manager->InsertBlock(block.start, end);
469 if (!block.branch.ignore) {
470 InsertBranch(*state.manager, block.branch);
471 }
472 }
473 state.manager->Decompile();
474}
475
476std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
477 u32 start_address,
478 const CompilerSettings& settings) {
479 auto result_out = std::make_unique<ShaderCharacteristics>();
480 if (settings.depth == CompileDepth::BruteForce) {
481 result_out->settings.depth = CompileDepth::BruteForce;
482 return result_out;
483 }
421 484
485 CFGRebuildState state{program_code, program_size, start_address};
422 // Inspect Code and generate blocks 486 // Inspect Code and generate blocks
423 state.labels.clear(); 487 state.labels.clear();
424 state.labels.emplace(start_address); 488 state.labels.emplace(start_address);
425 state.inspect_queries.push_back(state.start); 489 state.inspect_queries.push_back(state.start);
426 while (!state.inspect_queries.empty()) { 490 while (!state.inspect_queries.empty()) {
427 if (!TryInspectAddress(state)) { 491 if (!TryInspectAddress(state)) {
428 return {}; 492 result_out->settings.depth = CompileDepth::BruteForce;
493 return result_out;
429 } 494 }
430 } 495 }
431 496
432 // Decompile Stacks 497 bool use_flow_stack = true;
433 state.queries.push_back(Query{state.start, {}, {}}); 498
434 bool decompiled = true; 499 bool decompiled = false;
435 while (!state.queries.empty()) { 500
436 if (!TryQuery(state)) { 501 if (settings.depth != CompileDepth::FlowStack) {
437 decompiled = false; 502 // Decompile Stacks
438 break; 503 state.queries.push_back(Query{state.start, {}, {}});
504 decompiled = true;
505 while (!state.queries.empty()) {
506 if (!TryQuery(state)) {
507 decompiled = false;
508 break;
509 }
439 } 510 }
440 } 511 }
441 512
513 use_flow_stack = !decompiled;
514
442 // Sort and organize results 515 // Sort and organize results
443 std::sort(state.block_info.begin(), state.block_info.end(), 516 std::sort(state.block_info.begin(), state.block_info.end(),
444 [](const BlockInfo& a, const BlockInfo& b) { return a.start < b.start; }); 517 [](const BlockInfo& a, const BlockInfo& b) -> bool { return a.start < b.start; });
445 ShaderCharacteristics result_out{}; 518 if (decompiled && settings.depth != CompileDepth::NoFlowStack) {
446 result_out.decompilable = decompiled; 519 ASTManager manager{settings.depth != CompileDepth::DecompileBackwards,
447 result_out.start = start_address; 520 settings.disable_else_derivation};
448 result_out.end = start_address; 521 state.manager = &manager;
449 for (const auto& block : state.block_info) { 522 DecompileShader(state);
523 decompiled = state.manager->IsFullyDecompiled();
524 if (!decompiled) {
525 if (settings.depth == CompileDepth::FullDecompile) {
526 LOG_CRITICAL(HW_GPU, "Failed to remove all the gotos!:");
527 } else {
528 LOG_CRITICAL(HW_GPU, "Failed to remove all backward gotos!:");
529 }
530 state.manager->ShowCurrentState("Of Shader");
531 state.manager->Clear();
532 } else {
533 auto characteristics = std::make_unique<ShaderCharacteristics>();
534 characteristics->start = start_address;
535 characteristics->settings.depth = settings.depth;
536 characteristics->manager = std::move(manager);
537 characteristics->end = state.block_info.back().end + 1;
538 return characteristics;
539 }
540 }
541
542 result_out->start = start_address;
543 result_out->settings.depth =
544 use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
545 result_out->blocks.clear();
546 for (auto& block : state.block_info) {
450 ShaderBlock new_block{}; 547 ShaderBlock new_block{};
451 new_block.start = block.start; 548 new_block.start = block.start;
452 new_block.end = block.end; 549 new_block.end = block.end;
@@ -456,26 +553,26 @@ std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
456 new_block.branch.kills = block.branch.kill; 553 new_block.branch.kills = block.branch.kill;
457 new_block.branch.address = block.branch.address; 554 new_block.branch.address = block.branch.address;
458 } 555 }
459 result_out.end = std::max(result_out.end, block.end); 556 result_out->end = std::max(result_out->end, block.end);
460 result_out.blocks.push_back(new_block); 557 result_out->blocks.push_back(new_block);
461 } 558 }
462 if (result_out.decompilable) { 559 if (!use_flow_stack) {
463 result_out.labels = std::move(state.labels); 560 result_out->labels = std::move(state.labels);
464 return {std::move(result_out)}; 561 return result_out;
465 } 562 }
466 563
467 // If it's not decompilable, merge the unlabelled blocks together 564 auto back = result_out->blocks.begin();
468 auto back = result_out.blocks.begin();
469 auto next = std::next(back); 565 auto next = std::next(back);
470 while (next != result_out.blocks.end()) { 566 while (next != result_out->blocks.end()) {
471 if (state.labels.count(next->start) == 0 && next->start == back->end + 1) { 567 if (state.labels.count(next->start) == 0 && next->start == back->end + 1) {
472 back->end = next->end; 568 back->end = next->end;
473 next = result_out.blocks.erase(next); 569 next = result_out->blocks.erase(next);
474 continue; 570 continue;
475 } 571 }
476 back = next; 572 back = next;
477 ++next; 573 ++next;
478 } 574 }
479 return {std::move(result_out)}; 575
576 return result_out;
480} 577}
481} // namespace VideoCommon::Shader 578} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h
index b0a5e4f8c..74e54a5c7 100644
--- a/src/video_core/shader/control_flow.h
+++ b/src/video_core/shader/control_flow.h
@@ -6,9 +6,11 @@
6 6
7#include <list> 7#include <list>
8#include <optional> 8#include <optional>
9#include <unordered_set> 9#include <set>
10 10
11#include "video_core/engines/shader_bytecode.h" 11#include "video_core/engines/shader_bytecode.h"
12#include "video_core/shader/ast.h"
13#include "video_core/shader/compiler_settings.h"
12#include "video_core/shader/shader_ir.h" 14#include "video_core/shader/shader_ir.h"
13 15
14namespace VideoCommon::Shader { 16namespace VideoCommon::Shader {
@@ -67,13 +69,15 @@ struct ShaderBlock {
67 69
68struct ShaderCharacteristics { 70struct ShaderCharacteristics {
69 std::list<ShaderBlock> blocks{}; 71 std::list<ShaderBlock> blocks{};
70 bool decompilable{}; 72 std::set<u32> labels{};
71 u32 start{}; 73 u32 start{};
72 u32 end{}; 74 u32 end{};
73 std::unordered_set<u32> labels{}; 75 ASTManager manager{true, true};
76 CompilerSettings settings{};
74}; 77};
75 78
76std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, 79std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
77 std::size_t program_size, u32 start_address); 80 u32 start_address,
81 const CompilerSettings& settings);
78 82
79} // namespace VideoCommon::Shader 83} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index 47a9fd961..2626b1616 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -35,58 +35,138 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
35 35
36} // namespace 36} // namespace
37 37
38class ASTDecoder {
39public:
40 ASTDecoder(ShaderIR& ir) : ir(ir) {}
41
42 void operator()(ASTProgram& ast) {
43 ASTNode current = ast.nodes.GetFirst();
44 while (current) {
45 Visit(current);
46 current = current->GetNext();
47 }
48 }
49
50 void operator()(ASTIfThen& ast) {
51 ASTNode current = ast.nodes.GetFirst();
52 while (current) {
53 Visit(current);
54 current = current->GetNext();
55 }
56 }
57
58 void operator()(ASTIfElse& ast) {
59 ASTNode current = ast.nodes.GetFirst();
60 while (current) {
61 Visit(current);
62 current = current->GetNext();
63 }
64 }
65
66 void operator()(ASTBlockEncoded& ast) {}
67
68 void operator()(ASTBlockDecoded& ast) {}
69
70 void operator()(ASTVarSet& ast) {}
71
72 void operator()(ASTLabel& ast) {}
73
74 void operator()(ASTGoto& ast) {}
75
76 void operator()(ASTDoWhile& ast) {
77 ASTNode current = ast.nodes.GetFirst();
78 while (current) {
79 Visit(current);
80 current = current->GetNext();
81 }
82 }
83
84 void operator()(ASTReturn& ast) {}
85
86 void operator()(ASTBreak& ast) {}
87
88 void Visit(ASTNode& node) {
89 std::visit(*this, *node->GetInnerData());
90 if (node->IsBlockEncoded()) {
91 auto block = std::get_if<ASTBlockEncoded>(node->GetInnerData());
92 NodeBlock bb = ir.DecodeRange(block->start, block->end);
93 node->TransformBlockEncoded(std::move(bb));
94 }
95 }
96
97private:
98 ShaderIR& ir;
99};
100
38void ShaderIR::Decode() { 101void ShaderIR::Decode() {
39 std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); 102 std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
40 103
41 disable_flow_stack = false; 104 decompiled = false;
42 const auto info = ScanFlow(program_code, program_size, main_offset); 105 auto info = ScanFlow(program_code, program_size, main_offset, settings);
43 if (info) { 106 auto& shader_info = *info;
44 const auto& shader_info = *info; 107 coverage_begin = shader_info.start;
45 coverage_begin = shader_info.start; 108 coverage_end = shader_info.end;
46 coverage_end = shader_info.end; 109 switch (shader_info.settings.depth) {
47 if (shader_info.decompilable) { 110 case CompileDepth::FlowStack: {
48 disable_flow_stack = true;
49 const auto insert_block = [this](NodeBlock& nodes, u32 label) {
50 if (label == static_cast<u32>(exit_branch)) {
51 return;
52 }
53 basic_blocks.insert({label, nodes});
54 };
55 const auto& blocks = shader_info.blocks;
56 NodeBlock current_block;
57 u32 current_label = static_cast<u32>(exit_branch);
58 for (auto& block : blocks) {
59 if (shader_info.labels.count(block.start) != 0) {
60 insert_block(current_block, current_label);
61 current_block.clear();
62 current_label = block.start;
63 }
64 if (!block.ignore_branch) {
65 DecodeRangeInner(current_block, block.start, block.end);
66 InsertControlFlow(current_block, block);
67 } else {
68 DecodeRangeInner(current_block, block.start, block.end + 1);
69 }
70 }
71 insert_block(current_block, current_label);
72 return;
73 }
74 LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method");
75 // we can't decompile it, fallback to standard method
76 for (const auto& block : shader_info.blocks) { 111 for (const auto& block : shader_info.blocks) {
77 basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)}); 112 basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)});
78 } 113 }
79 return; 114 break;
80 } 115 }
81 LOG_WARNING(HW_GPU, "Flow Analysis Failed! Falling back to brute force compiling"); 116 case CompileDepth::NoFlowStack: {
82 117 disable_flow_stack = true;
83 // Now we need to deal with an undecompilable shader. We need to brute force 118 const auto insert_block = [this](NodeBlock& nodes, u32 label) {
84 // a shader that captures every position. 119 if (label == static_cast<u32>(exit_branch)) {
85 coverage_begin = main_offset; 120 return;
86 const u32 shader_end = static_cast<u32>(program_size / sizeof(u64)); 121 }
87 coverage_end = shader_end; 122 basic_blocks.insert({label, nodes});
88 for (u32 label = main_offset; label < shader_end; label++) { 123 };
89 basic_blocks.insert({label, DecodeRange(label, label + 1)}); 124 const auto& blocks = shader_info.blocks;
125 NodeBlock current_block;
126 u32 current_label = static_cast<u32>(exit_branch);
127 for (auto& block : blocks) {
128 if (shader_info.labels.count(block.start) != 0) {
129 insert_block(current_block, current_label);
130 current_block.clear();
131 current_label = block.start;
132 }
133 if (!block.ignore_branch) {
134 DecodeRangeInner(current_block, block.start, block.end);
135 InsertControlFlow(current_block, block);
136 } else {
137 DecodeRangeInner(current_block, block.start, block.end + 1);
138 }
139 }
140 insert_block(current_block, current_label);
141 break;
142 }
143 case CompileDepth::DecompileBackwards:
144 case CompileDepth::FullDecompile: {
145 program_manager = std::move(shader_info.manager);
146 disable_flow_stack = true;
147 decompiled = true;
148 ASTDecoder decoder{*this};
149 ASTNode program = GetASTProgram();
150 decoder.Visit(program);
151 break;
152 }
153 default:
154 LOG_CRITICAL(HW_GPU, "Unknown decompilation mode!");
155 [[fallthrough]];
156 case CompileDepth::BruteForce: {
157 coverage_begin = main_offset;
158 const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
159 coverage_end = shader_end;
160 for (u32 label = main_offset; label < shader_end; label++) {
161 basic_blocks.insert({label, DecodeRange(label, label + 1)});
162 }
163 break;
164 }
165 }
166 if (settings.depth != shader_info.settings.depth) {
167 LOG_WARNING(
168 HW_GPU, "Decompiling to this setting \"{}\" failed, downgrading to this setting \"{}\"",
169 CompileDepthAsString(settings.depth), CompileDepthAsString(shader_info.settings.depth));
90 } 170 }
91} 171}
92 172
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index 840694527..fec8f2dbe 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h"
7#include "video_core/engines/shader_bytecode.h" 8#include "video_core/engines/shader_bytecode.h"
8#include "video_core/shader/node_helper.h" 9#include "video_core/shader/node_helper.h"
9#include "video_core/shader/shader_ir.h" 10#include "video_core/shader/shader_ir.h"
@@ -18,7 +19,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
18 const Instruction instr = {program_code[pc]}; 19 const Instruction instr = {program_code[pc]};
19 const auto opcode = OpCode::Decode(instr); 20 const auto opcode = OpCode::Decode(instr);
20 21
21 DEBUG_ASSERT(instr.hsetp2.ftz == 0); 22 LOG_DEBUG(HW_GPU, "ftz={}", static_cast<u32>(instr.hsetp2.ftz));
22 23
23 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a); 24 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a);
24 op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a); 25 op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a);
@@ -32,6 +33,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
32 h_and = instr.hsetp2.cbuf_and_imm.h_and; 33 h_and = instr.hsetp2.cbuf_and_imm.h_and;
33 op_b = GetOperandAbsNegHalf(GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()), 34 op_b = GetOperandAbsNegHalf(GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
34 instr.hsetp2.cbuf.abs_b, instr.hsetp2.cbuf.negate_b); 35 instr.hsetp2.cbuf.abs_b, instr.hsetp2.cbuf.negate_b);
36 // F32 is hardcoded in hardware
37 op_b = UnpackHalfFloat(std::move(op_b), Tegra::Shader::HalfType::F32);
35 break; 38 break;
36 case OpCode::Id::HSETP2_IMM: 39 case OpCode::Id::HSETP2_IMM:
37 cond = instr.hsetp2.cbuf_and_imm.cond; 40 cond = instr.hsetp2.cbuf_and_imm.cond;
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index d54fb88c9..95ec1cdd9 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -41,11 +41,46 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
41 const Instruction instr = {program_code[pc]}; 41 const Instruction instr = {program_code[pc]};
42 const auto opcode = OpCode::Decode(instr); 42 const auto opcode = OpCode::Decode(instr);
43 43
44 const auto GetCoordinates = [this, instr](Tegra::Shader::ImageType image_type) {
45 std::vector<Node> coords;
46 const std::size_t num_coords{GetImageTypeNumCoordinates(image_type)};
47 coords.reserve(num_coords);
48 for (std::size_t i = 0; i < num_coords; ++i) {
49 coords.push_back(GetRegister(instr.gpr8.Value() + i));
50 }
51 return coords;
52 };
53
44 switch (opcode->get().GetId()) { 54 switch (opcode->get().GetId()) {
55 case OpCode::Id::SULD: {
56 UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P);
57 UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store !=
58 Tegra::Shader::OutOfBoundsStore::Ignore);
59
60 const auto type{instr.suldst.image_type};
61 auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type)
62 : GetBindlessImage(instr.gpr39, type)};
63 image.MarkRead();
64
65 u32 indexer = 0;
66 for (u32 element = 0; element < 4; ++element) {
67 if (!instr.suldst.IsComponentEnabled(element)) {
68 continue;
69 }
70 MetaImage meta{image, {}, element};
71 Node value = Operation(OperationCode::ImageLoad, meta, GetCoordinates(type));
72 SetTemporary(bb, indexer++, std::move(value));
73 }
74 for (u32 i = 0; i < indexer; ++i) {
75 SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
76 }
77 break;
78 }
45 case OpCode::Id::SUST: { 79 case OpCode::Id::SUST: {
46 UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); 80 UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P);
47 UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); 81 UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store !=
48 UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store 82 Tegra::Shader::OutOfBoundsStore::Ignore);
83 UNIMPLEMENTED_IF(instr.suldst.component_mask_selector != 0xf); // Ensure we have RGBA
49 84
50 std::vector<Node> values; 85 std::vector<Node> values;
51 constexpr std::size_t hardcoded_size{4}; 86 constexpr std::size_t hardcoded_size{4};
@@ -53,58 +88,51 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
53 values.push_back(GetRegister(instr.gpr0.Value() + i)); 88 values.push_back(GetRegister(instr.gpr0.Value() + i));
54 } 89 }
55 90
56 std::vector<Node> coords; 91 const auto type{instr.suldst.image_type};
57 const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; 92 auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type)
58 for (std::size_t i = 0; i < num_coords; ++i) { 93 : GetBindlessImage(instr.gpr39, type)};
59 coords.push_back(GetRegister(instr.gpr8.Value() + i));
60 }
61
62 const auto type{instr.sust.image_type};
63 auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
64 : GetBindlessImage(instr.gpr39, type)};
65 image.MarkWrite(); 94 image.MarkWrite();
66 95
67 MetaImage meta{image, values}; 96 MetaImage meta{image, std::move(values)};
68 bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords))); 97 bb.push_back(Operation(OperationCode::ImageStore, meta, GetCoordinates(type)));
69 break; 98 break;
70 } 99 }
71 case OpCode::Id::SUATOM: { 100 case OpCode::Id::SUATOM: {
72 UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0); 101 UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0);
73 102
74 Node value = GetRegister(instr.gpr0);
75
76 std::vector<Node> coords;
77 const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)};
78 for (std::size_t i = 0; i < num_coords; ++i) {
79 coords.push_back(GetRegister(instr.gpr8.Value() + i));
80 }
81
82 const OperationCode operation_code = [instr] { 103 const OperationCode operation_code = [instr] {
83 switch (instr.suatom_d.operation) { 104 switch (instr.suatom_d.operation_type) {
84 case Tegra::Shader::ImageAtomicOperation::Add: 105 case Tegra::Shader::ImageAtomicOperationType::S32:
85 return OperationCode::AtomicImageAdd; 106 case Tegra::Shader::ImageAtomicOperationType::U32:
86 case Tegra::Shader::ImageAtomicOperation::Min: 107 switch (instr.suatom_d.operation) {
87 return OperationCode::AtomicImageMin; 108 case Tegra::Shader::ImageAtomicOperation::Add:
88 case Tegra::Shader::ImageAtomicOperation::Max: 109 return OperationCode::AtomicImageAdd;
89 return OperationCode::AtomicImageMax; 110 case Tegra::Shader::ImageAtomicOperation::And:
90 case Tegra::Shader::ImageAtomicOperation::And: 111 return OperationCode::AtomicImageAnd;
91 return OperationCode::AtomicImageAnd; 112 case Tegra::Shader::ImageAtomicOperation::Or:
92 case Tegra::Shader::ImageAtomicOperation::Or: 113 return OperationCode::AtomicImageOr;
93 return OperationCode::AtomicImageOr; 114 case Tegra::Shader::ImageAtomicOperation::Xor:
94 case Tegra::Shader::ImageAtomicOperation::Xor: 115 return OperationCode::AtomicImageXor;
95 return OperationCode::AtomicImageXor; 116 case Tegra::Shader::ImageAtomicOperation::Exch:
96 case Tegra::Shader::ImageAtomicOperation::Exch: 117 return OperationCode::AtomicImageExchange;
97 return OperationCode::AtomicImageExchange; 118 }
98 default: 119 default:
99 UNIMPLEMENTED_MSG("Unimplemented operation={}", 120 break;
100 static_cast<u32>(instr.suatom_d.operation.Value()));
101 return OperationCode::AtomicImageAdd;
102 } 121 }
122 UNIMPLEMENTED_MSG("Unimplemented operation={} type={}",
123 static_cast<u64>(instr.suatom_d.operation.Value()),
124 static_cast<u64>(instr.suatom_d.operation_type.Value()));
125 return OperationCode::AtomicImageAdd;
103 }(); 126 }();
104 127
105 const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)}; 128 Node value = GetRegister(instr.gpr0);
129
130 const auto type = instr.suatom_d.image_type;
131 auto& image = GetImage(instr.image, type);
132 image.MarkAtomic();
133
106 MetaImage meta{image, {std::move(value)}}; 134 MetaImage meta{image, {std::move(value)}};
107 SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords))); 135 SetRegister(bb, instr.gpr0, Operation(operation_code, meta, GetCoordinates(type)));
108 break; 136 break;
109 } 137 }
110 default: 138 default:
@@ -114,35 +142,32 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
114 return pc; 142 return pc;
115} 143}
116 144
117Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, 145Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
118 std::optional<Tegra::Shader::ImageAtomicSize> size) {
119 const auto offset{static_cast<std::size_t>(image.index.Value())}; 146 const auto offset{static_cast<std::size_t>(image.index.Value())};
120 if (const auto image = TryUseExistingImage(offset, type, size)) { 147 if (const auto image = TryUseExistingImage(offset, type)) {
121 return *image; 148 return *image;
122 } 149 }
123 150
124 const std::size_t next_index{used_images.size()}; 151 const std::size_t next_index{used_images.size()};
125 return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second; 152 return used_images.emplace(offset, Image{offset, next_index, type}).first->second;
126} 153}
127 154
128Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, 155Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
129 std::optional<Tegra::Shader::ImageAtomicSize> size) {
130 const Node image_register{GetRegister(reg)}; 156 const Node image_register{GetRegister(reg)};
131 const auto [base_image, cbuf_index, cbuf_offset]{ 157 const auto [base_image, cbuf_index, cbuf_offset]{
132 TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; 158 TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))};
133 const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; 159 const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)};
134 160
135 if (const auto image = TryUseExistingImage(cbuf_key, type, size)) { 161 if (const auto image = TryUseExistingImage(cbuf_key, type)) {
136 return *image; 162 return *image;
137 } 163 }
138 164
139 const std::size_t next_index{used_images.size()}; 165 const std::size_t next_index{used_images.size()};
140 return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size}) 166 return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type})
141 .first->second; 167 .first->second;
142} 168}
143 169
144Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, 170Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type) {
145 std::optional<Tegra::Shader::ImageAtomicSize> size) {
146 auto it = used_images.find(offset); 171 auto it = used_images.find(offset);
147 if (it == used_images.end()) { 172 if (it == used_images.end()) {
148 return nullptr; 173 return nullptr;
@@ -150,14 +175,6 @@ Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
150 auto& image = it->second; 175 auto& image = it->second;
151 ASSERT(image.GetType() == type); 176 ASSERT(image.GetType() == type);
152 177
153 if (size) {
154 // We know the size, if it's known it has to be the same as before, otherwise we can set it.
155 if (image.IsSizeKnown()) {
156 ASSERT(image.GetSize() == size);
157 } else {
158 image.SetSize(*size);
159 }
160 }
161 return &image; 178 return &image;
162} 179}
163 180
diff --git a/src/video_core/shader/expr.cpp b/src/video_core/shader/expr.cpp
new file mode 100644
index 000000000..2647865d4
--- /dev/null
+++ b/src/video_core/shader/expr.cpp
@@ -0,0 +1,93 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include <variant>
7
8#include "video_core/shader/expr.h"
9
10namespace VideoCommon::Shader {
11namespace {
12bool ExprIsBoolean(const Expr& expr) {
13 return std::holds_alternative<ExprBoolean>(*expr);
14}
15
16bool ExprBooleanGet(const Expr& expr) {
17 return std::get_if<ExprBoolean>(expr.get())->value;
18}
19} // Anonymous namespace
20
21bool ExprAnd::operator==(const ExprAnd& b) const {
22 return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
23}
24
25bool ExprAnd::operator!=(const ExprAnd& b) const {
26 return !operator==(b);
27}
28
29bool ExprOr::operator==(const ExprOr& b) const {
30 return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
31}
32
33bool ExprOr::operator!=(const ExprOr& b) const {
34 return !operator==(b);
35}
36
37bool ExprNot::operator==(const ExprNot& b) const {
38 return *operand1 == *b.operand1;
39}
40
41bool ExprNot::operator!=(const ExprNot& b) const {
42 return !operator==(b);
43}
44
45Expr MakeExprNot(Expr first) {
46 if (std::holds_alternative<ExprNot>(*first)) {
47 return std::get_if<ExprNot>(first.get())->operand1;
48 }
49 return MakeExpr<ExprNot>(std::move(first));
50}
51
52Expr MakeExprAnd(Expr first, Expr second) {
53 if (ExprIsBoolean(first)) {
54 return ExprBooleanGet(first) ? second : first;
55 }
56 if (ExprIsBoolean(second)) {
57 return ExprBooleanGet(second) ? first : second;
58 }
59 return MakeExpr<ExprAnd>(std::move(first), std::move(second));
60}
61
62Expr MakeExprOr(Expr first, Expr second) {
63 if (ExprIsBoolean(first)) {
64 return ExprBooleanGet(first) ? first : second;
65 }
66 if (ExprIsBoolean(second)) {
67 return ExprBooleanGet(second) ? second : first;
68 }
69 return MakeExpr<ExprOr>(std::move(first), std::move(second));
70}
71
72bool ExprAreEqual(const Expr& first, const Expr& second) {
73 return (*first) == (*second);
74}
75
76bool ExprAreOpposite(const Expr& first, const Expr& second) {
77 if (std::holds_alternative<ExprNot>(*first)) {
78 return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
79 }
80 if (std::holds_alternative<ExprNot>(*second)) {
81 return ExprAreEqual(std::get_if<ExprNot>(second.get())->operand1, first);
82 }
83 return false;
84}
85
86bool ExprIsTrue(const Expr& first) {
87 if (ExprIsBoolean(first)) {
88 return ExprBooleanGet(first);
89 }
90 return false;
91}
92
93} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h
new file mode 100644
index 000000000..d3dcd00ec
--- /dev/null
+++ b/src/video_core/shader/expr.h
@@ -0,0 +1,139 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <variant>
9
10#include "video_core/engines/shader_bytecode.h"
11
12namespace VideoCommon::Shader {
13
14using Tegra::Shader::ConditionCode;
15using Tegra::Shader::Pred;
16
17class ExprAnd;
18class ExprBoolean;
19class ExprCondCode;
20class ExprNot;
21class ExprOr;
22class ExprPredicate;
23class ExprVar;
24
25using ExprData =
26 std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
27using Expr = std::shared_ptr<ExprData>;
28
29class ExprAnd final {
30public:
31 explicit ExprAnd(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
32
33 bool operator==(const ExprAnd& b) const;
34 bool operator!=(const ExprAnd& b) const;
35
36 Expr operand1;
37 Expr operand2;
38};
39
40class ExprOr final {
41public:
42 explicit ExprOr(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
43
44 bool operator==(const ExprOr& b) const;
45 bool operator!=(const ExprOr& b) const;
46
47 Expr operand1;
48 Expr operand2;
49};
50
51class ExprNot final {
52public:
53 explicit ExprNot(Expr a) : operand1{std::move(a)} {}
54
55 bool operator==(const ExprNot& b) const;
56 bool operator!=(const ExprNot& b) const;
57
58 Expr operand1;
59};
60
61class ExprVar final {
62public:
63 explicit ExprVar(u32 index) : var_index{index} {}
64
65 bool operator==(const ExprVar& b) const {
66 return var_index == b.var_index;
67 }
68
69 bool operator!=(const ExprVar& b) const {
70 return !operator==(b);
71 }
72
73 u32 var_index;
74};
75
76class ExprPredicate final {
77public:
78 explicit ExprPredicate(u32 predicate) : predicate{predicate} {}
79
80 bool operator==(const ExprPredicate& b) const {
81 return predicate == b.predicate;
82 }
83
84 bool operator!=(const ExprPredicate& b) const {
85 return !operator==(b);
86 }
87
88 u32 predicate;
89};
90
91class ExprCondCode final {
92public:
93 explicit ExprCondCode(ConditionCode cc) : cc{cc} {}
94
95 bool operator==(const ExprCondCode& b) const {
96 return cc == b.cc;
97 }
98
99 bool operator!=(const ExprCondCode& b) const {
100 return !operator==(b);
101 }
102
103 ConditionCode cc;
104};
105
106class ExprBoolean final {
107public:
108 explicit ExprBoolean(bool val) : value{val} {}
109
110 bool operator==(const ExprBoolean& b) const {
111 return value == b.value;
112 }
113
114 bool operator!=(const ExprBoolean& b) const {
115 return !operator==(b);
116 }
117
118 bool value;
119};
120
121template <typename T, typename... Args>
122Expr MakeExpr(Args&&... args) {
123 static_assert(std::is_convertible_v<T, ExprData>);
124 return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
125}
126
127bool ExprAreEqual(const Expr& first, const Expr& second);
128
129bool ExprAreOpposite(const Expr& first, const Expr& second);
130
131Expr MakeExprNot(Expr first);
132
133Expr MakeExprAnd(Expr first, Expr second);
134
135Expr MakeExprOr(Expr first, Expr second);
136
137bool ExprIsTrue(const Expr& first);
138
139} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index abf2cb1ab..338bab17c 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -149,10 +149,10 @@ enum class OperationCode {
149 TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 149 TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
150 TexelFetch, /// (MetaTexture, int[N], int) -> float4 150 TexelFetch, /// (MetaTexture, int[N], int) -> float4
151 151
152 ImageStore, /// (MetaImage, int[N] values) -> void 152 ImageLoad, /// (MetaImage, int[N] coords) -> void
153 ImageStore, /// (MetaImage, int[N] coords) -> void
154
153 AtomicImageAdd, /// (MetaImage, int[N] coords) -> void 155 AtomicImageAdd, /// (MetaImage, int[N] coords) -> void
154 AtomicImageMin, /// (MetaImage, int[N] coords) -> void
155 AtomicImageMax, /// (MetaImage, int[N] coords) -> void
156 AtomicImageAnd, /// (MetaImage, int[N] coords) -> void 156 AtomicImageAnd, /// (MetaImage, int[N] coords) -> void
157 AtomicImageOr, /// (MetaImage, int[N] coords) -> void 157 AtomicImageOr, /// (MetaImage, int[N] coords) -> void
158 AtomicImageXor, /// (MetaImage, int[N] coords) -> void 158 AtomicImageXor, /// (MetaImage, int[N] coords) -> void
@@ -294,21 +294,18 @@ private:
294 294
295class Image final { 295class Image final {
296public: 296public:
297 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, 297 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type)
298 std::optional<Tegra::Shader::ImageAtomicSize> size) 298 : offset{offset}, index{index}, type{type}, is_bindless{false} {}
299 : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {}
300 299
301 constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, 300 constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
302 Tegra::Shader::ImageType type, 301 Tegra::Shader::ImageType type)
303 std::optional<Tegra::Shader::ImageAtomicSize> size)
304 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, 302 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
305 is_bindless{true}, size{size} {} 303 is_bindless{true} {}
306 304
307 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, 305 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
308 bool is_bindless, bool is_written, bool is_read, 306 bool is_bindless, bool is_written, bool is_read, bool is_atomic)
309 std::optional<Tegra::Shader::ImageAtomicSize> size)
310 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, 307 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless},
311 is_written{is_written}, is_read{is_read}, size{size} {} 308 is_written{is_written}, is_read{is_read}, is_atomic{is_atomic} {}
312 309
313 void MarkWrite() { 310 void MarkWrite() {
314 is_written = true; 311 is_written = true;
@@ -318,8 +315,10 @@ public:
318 is_read = true; 315 is_read = true;
319 } 316 }
320 317
321 void SetSize(Tegra::Shader::ImageAtomicSize size_) { 318 void MarkAtomic() {
322 size = size_; 319 MarkWrite();
320 MarkRead();
321 is_atomic = true;
323 } 322 }
324 323
325 constexpr std::size_t GetOffset() const { 324 constexpr std::size_t GetOffset() const {
@@ -346,21 +345,17 @@ public:
346 return is_read; 345 return is_read;
347 } 346 }
348 347
349 constexpr std::pair<u32, u32> GetBindlessCBuf() const { 348 constexpr bool IsAtomic() const {
350 return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; 349 return is_atomic;
351 } 350 }
352 351
353 constexpr bool IsSizeKnown() const { 352 constexpr std::pair<u32, u32> GetBindlessCBuf() const {
354 return size.has_value(); 353 return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
355 }
356
357 constexpr Tegra::Shader::ImageAtomicSize GetSize() const {
358 return size.value();
359 } 354 }
360 355
361 constexpr bool operator<(const Image& rhs) const { 356 constexpr bool operator<(const Image& rhs) const {
362 return std::tie(offset, index, type, size, is_bindless) < 357 return std::tie(offset, index, type, is_bindless) <
363 std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless); 358 std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless);
364 } 359 }
365 360
366private: 361private:
@@ -370,7 +365,7 @@ private:
370 bool is_bindless{}; 365 bool is_bindless{};
371 bool is_written{}; 366 bool is_written{};
372 bool is_read{}; 367 bool is_read{};
373 std::optional<Tegra::Shader::ImageAtomicSize> size{}; 368 bool is_atomic{};
374}; 369};
375 370
376struct GlobalMemoryBase { 371struct GlobalMemoryBase {
@@ -402,6 +397,7 @@ struct MetaTexture {
402struct MetaImage { 397struct MetaImage {
403 const Image& image; 398 const Image& image;
404 std::vector<Node> values; 399 std::vector<Node> values;
400 u32 element{};
405}; 401};
406 402
407/// Parameters that modify an operation but are not part of any particular operand 403/// Parameters that modify an operation but are not part of any particular operand
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 2c357f310..c1f2b88c8 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -22,8 +22,10 @@ using Tegra::Shader::PredCondition;
22using Tegra::Shader::PredOperation; 22using Tegra::Shader::PredOperation;
23using Tegra::Shader::Register; 23using Tegra::Shader::Register;
24 24
25ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size) 25ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size,
26 : program_code{program_code}, main_offset{main_offset}, program_size{size} { 26 CompilerSettings settings)
27 : program_code{program_code}, main_offset{main_offset}, program_size{size}, basic_blocks{},
28 program_manager{true, true}, settings{settings} {
27 Decode(); 29 Decode();
28} 30}
29 31
@@ -137,7 +139,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
137 return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer)); 139 return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer));
138} 140}
139 141
140Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) { 142Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const {
141 const Node node = MakeNode<InternalFlagNode>(flag); 143 const Node node = MakeNode<InternalFlagNode>(flag);
142 if (negated) { 144 if (negated) {
143 return Operation(OperationCode::LogicalNegate, node); 145 return Operation(OperationCode::LogicalNegate, node);
@@ -367,13 +369,13 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
367 return op->second; 369 return op->second;
368} 370}
369 371
370Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) { 372Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const {
371 switch (cc) { 373 switch (cc) {
372 case Tegra::Shader::ConditionCode::NEU: 374 case Tegra::Shader::ConditionCode::NEU:
373 return GetInternalFlag(InternalFlag::Zero, true); 375 return GetInternalFlag(InternalFlag::Zero, true);
374 default: 376 default:
375 UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc)); 377 UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc));
376 return GetPredicate(static_cast<u64>(Pred::NeverExecute)); 378 return MakeNode<PredicateNode>(Pred::NeverExecute, false);
377 } 379 }
378} 380}
379 381
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 2f03d83ba..105981d67 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -15,6 +15,8 @@
15#include "video_core/engines/maxwell_3d.h" 15#include "video_core/engines/maxwell_3d.h"
16#include "video_core/engines/shader_bytecode.h" 16#include "video_core/engines/shader_bytecode.h"
17#include "video_core/engines/shader_header.h" 17#include "video_core/engines/shader_header.h"
18#include "video_core/shader/ast.h"
19#include "video_core/shader/compiler_settings.h"
18#include "video_core/shader/node.h" 20#include "video_core/shader/node.h"
19 21
20namespace VideoCommon::Shader { 22namespace VideoCommon::Shader {
@@ -64,7 +66,8 @@ struct GlobalMemoryUsage {
64 66
65class ShaderIR final { 67class ShaderIR final {
66public: 68public:
67 explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size); 69 explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size,
70 CompilerSettings settings);
68 ~ShaderIR(); 71 ~ShaderIR();
69 72
70 const std::map<u32, NodeBlock>& GetBasicBlocks() const { 73 const std::map<u32, NodeBlock>& GetBasicBlocks() const {
@@ -144,11 +147,31 @@ public:
144 return disable_flow_stack; 147 return disable_flow_stack;
145 } 148 }
146 149
150 bool IsDecompiled() const {
151 return decompiled;
152 }
153
154 const ASTManager& GetASTManager() const {
155 return program_manager;
156 }
157
158 ASTNode GetASTProgram() const {
159 return program_manager.GetProgram();
160 }
161
162 u32 GetASTNumVariables() const {
163 return program_manager.GetVariables();
164 }
165
147 u32 ConvertAddressToNvidiaSpace(const u32 address) const { 166 u32 ConvertAddressToNvidiaSpace(const u32 address) const {
148 return (address - main_offset) * sizeof(Tegra::Shader::Instruction); 167 return (address - main_offset) * sizeof(Tegra::Shader::Instruction);
149 } 168 }
150 169
170 /// Returns a condition code evaluated from internal flags
171 Node GetConditionCode(Tegra::Shader::ConditionCode cc) const;
172
151private: 173private:
174 friend class ASTDecoder;
152 void Decode(); 175 void Decode();
153 176
154 NodeBlock DecodeRange(u32 begin, u32 end); 177 NodeBlock DecodeRange(u32 begin, u32 end);
@@ -213,7 +236,7 @@ private:
213 /// Generates a node representing an output attribute. Keeps track of used attributes. 236 /// Generates a node representing an output attribute. Keeps track of used attributes.
214 Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer); 237 Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
215 /// Generates a node representing an internal flag 238 /// Generates a node representing an internal flag
216 Node GetInternalFlag(InternalFlag flag, bool negated = false); 239 Node GetInternalFlag(InternalFlag flag, bool negated = false) const;
217 /// Generates a node representing a local memory address 240 /// Generates a node representing a local memory address
218 Node GetLocalMemory(Node address); 241 Node GetLocalMemory(Node address);
219 /// Generates a node representing a shared memory address 242 /// Generates a node representing a shared memory address
@@ -271,9 +294,6 @@ private:
271 /// Returns a predicate combiner operation 294 /// Returns a predicate combiner operation
272 OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation); 295 OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
273 296
274 /// Returns a condition code evaluated from internal flags
275 Node GetConditionCode(Tegra::Shader::ConditionCode cc);
276
277 /// Accesses a texture sampler 297 /// Accesses a texture sampler
278 const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler, 298 const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
279 Tegra::Shader::TextureType type, bool is_array, bool is_shadow); 299 Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
@@ -284,16 +304,13 @@ private:
284 bool is_shadow); 304 bool is_shadow);
285 305
286 /// Accesses an image. 306 /// Accesses an image.
287 Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, 307 Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
288 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
289 308
290 /// Access a bindless image sampler. 309 /// Access a bindless image sampler.
291 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, 310 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
292 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
293 311
294 /// Tries to access an existing image, updating it's state as needed 312 /// Tries to access an existing image, updating it's state as needed
295 Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, 313 Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type);
296 std::optional<Tegra::Shader::ImageAtomicSize> size);
297 314
298 /// Extracts a sequence of bits from a node 315 /// Extracts a sequence of bits from a node
299 Node BitfieldExtract(Node value, u32 offset, u32 bits); 316 Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -360,6 +377,7 @@ private:
360 const ProgramCode& program_code; 377 const ProgramCode& program_code;
361 const u32 main_offset; 378 const u32 main_offset;
362 const std::size_t program_size; 379 const std::size_t program_size;
380 bool decompiled{};
363 bool disable_flow_stack{}; 381 bool disable_flow_stack{};
364 382
365 u32 coverage_begin{}; 383 u32 coverage_begin{};
@@ -367,6 +385,8 @@ private:
367 385
368 std::map<u32, NodeBlock> basic_blocks; 386 std::map<u32, NodeBlock> basic_blocks;
369 NodeBlock global_code; 387 NodeBlock global_code;
388 ASTManager program_manager;
389 CompilerSettings settings{};
370 390
371 std::set<u32> used_registers; 391 std::set<u32> used_registers;
372 std::set<Tegra::Shader::Pred> used_predicates; 392 std::set<Tegra::Shader::Pred> used_predicates;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 877c6635d..ca2da8f97 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -224,8 +224,13 @@ public:
224 const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, 224 const Tegra::Engines::Fermi2D::Regs::Surface& dst_config,
225 const Tegra::Engines::Fermi2D::Config& copy_config) { 225 const Tegra::Engines::Fermi2D::Config& copy_config) {
226 std::lock_guard lock{mutex}; 226 std::lock_guard lock{mutex};
227 std::pair<TSurface, TView> dst_surface = GetFermiSurface(dst_config); 227 SurfaceParams src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
228 std::pair<TSurface, TView> src_surface = GetFermiSurface(src_config); 228 SurfaceParams dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
229 const GPUVAddr src_gpu_addr = src_config.Address();
230 const GPUVAddr dst_gpu_addr = dst_config.Address();
231 DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
232 std::pair<TSurface, TView> dst_surface = GetSurface(dst_gpu_addr, dst_params, true, false);
233 std::pair<TSurface, TView> src_surface = GetSurface(src_gpu_addr, src_params, true, false);
229 ImageBlit(src_surface.second, dst_surface.second, copy_config); 234 ImageBlit(src_surface.second, dst_surface.second, copy_config);
230 dst_surface.first->MarkAsModified(true, Tick()); 235 dst_surface.first->MarkAsModified(true, Tick());
231 } 236 }
@@ -357,6 +362,29 @@ private:
357 BufferCopy = 3, 362 BufferCopy = 3,
358 }; 363 };
359 364
365 enum class DeductionType : u32 {
366 DeductionComplete,
367 DeductionIncomplete,
368 DeductionFailed,
369 };
370
371 struct Deduction {
372 DeductionType type{DeductionType::DeductionFailed};
373 TSurface surface{};
374
375 bool Failed() const {
376 return type == DeductionType::DeductionFailed;
377 }
378
379 bool Incomplete() const {
380 return type == DeductionType::DeductionIncomplete;
381 }
382
383 bool IsDepth() const {
384 return surface->GetSurfaceParams().IsPixelFormatZeta();
385 }
386 };
387
360 /** 388 /**
361 * `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle. 389 * `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle.
362 * @param overlaps, the overlapping surfaces registered in the cache. 390 * @param overlaps, the overlapping surfaces registered in the cache.
@@ -691,6 +719,120 @@ private:
691 MatchTopologyResult::FullMatch); 719 MatchTopologyResult::FullMatch);
692 } 720 }
693 721
722 /**
723 * `DeduceSurface` gets the starting address and parameters of a candidate surface and tries
724 * to find a matching surface within the cache that's similar to it. If there are many textures
725 * or the texture found if entirely incompatible, it will fail. If no texture is found, the
726 * blit will be unsuccessful.
727 * @param gpu_addr, the starting address of the candidate surface.
728 * @param params, the paremeters on the candidate surface.
729 **/
730 Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
731 const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
732 const auto cache_addr{ToCacheAddr(host_ptr)};
733
734 if (!cache_addr) {
735 Deduction result{};
736 result.type = DeductionType::DeductionFailed;
737 return result;
738 }
739
740 if (const auto iter = l1_cache.find(cache_addr); iter != l1_cache.end()) {
741 TSurface& current_surface = iter->second;
742 const auto topological_result = current_surface->MatchesTopology(params);
743 if (topological_result != MatchTopologyResult::FullMatch) {
744 Deduction result{};
745 result.type = DeductionType::DeductionFailed;
746 return result;
747 }
748 const auto struct_result = current_surface->MatchesStructure(params);
749 if (struct_result != MatchStructureResult::None &&
750 current_surface->MatchTarget(params.target)) {
751 Deduction result{};
752 result.type = DeductionType::DeductionComplete;
753 result.surface = current_surface;
754 return result;
755 }
756 }
757
758 const std::size_t candidate_size = params.GetGuestSizeInBytes();
759 auto overlaps{GetSurfacesInRegion(cache_addr, candidate_size)};
760
761 if (overlaps.empty()) {
762 Deduction result{};
763 result.type = DeductionType::DeductionIncomplete;
764 return result;
765 }
766
767 if (overlaps.size() > 1) {
768 Deduction result{};
769 result.type = DeductionType::DeductionFailed;
770 return result;
771 } else {
772 Deduction result{};
773 result.type = DeductionType::DeductionComplete;
774 result.surface = overlaps[0];
775 return result;
776 }
777 }
778
779 /**
780 * `DeduceBestBlit` gets the a source and destination starting address and parameters,
781 * and tries to deduce if they are supposed to be depth textures. If so, their
782 * parameters are modified and fixed into so.
783 * @param gpu_addr, the starting address of the candidate surface.
784 * @param params, the parameters on the candidate surface.
785 **/
786 void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params,
787 const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) {
788 auto deduced_src = DeduceSurface(src_gpu_addr, src_params);
789 auto deduced_dst = DeduceSurface(src_gpu_addr, src_params);
790 if (deduced_src.Failed() || deduced_dst.Failed()) {
791 return;
792 }
793
794 const bool incomplete_src = deduced_src.Incomplete();
795 const bool incomplete_dst = deduced_dst.Incomplete();
796
797 if (incomplete_src && incomplete_dst) {
798 return;
799 }
800
801 const bool any_incomplete = incomplete_src || incomplete_dst;
802
803 if (!any_incomplete) {
804 if (!(deduced_src.IsDepth() && deduced_dst.IsDepth())) {
805 return;
806 }
807 } else {
808 if (incomplete_src && !(deduced_dst.IsDepth())) {
809 return;
810 }
811
812 if (incomplete_dst && !(deduced_src.IsDepth())) {
813 return;
814 }
815 }
816
817 const auto inherit_format = ([](SurfaceParams& to, TSurface from) {
818 const SurfaceParams& params = from->GetSurfaceParams();
819 to.pixel_format = params.pixel_format;
820 to.component_type = params.component_type;
821 to.type = params.type;
822 });
823 // Now we got the cases where one or both is Depth and the other is not known
824 if (!incomplete_src) {
825 inherit_format(src_params, deduced_src.surface);
826 } else {
827 inherit_format(src_params, deduced_dst.surface);
828 }
829 if (!incomplete_dst) {
830 inherit_format(dst_params, deduced_dst.surface);
831 } else {
832 inherit_format(dst_params, deduced_src.surface);
833 }
834 }
835
694 std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params, 836 std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params,
695 bool preserve_contents) { 837 bool preserve_contents) {
696 auto new_surface{GetUncachedSurface(gpu_addr, params)}; 838 auto new_surface{GetUncachedSurface(gpu_addr, params)};
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index dc6fa07fc..ff1c1d985 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -66,6 +66,9 @@ add_executable(yuzu
66 configuration/configure_profile_manager.cpp 66 configuration/configure_profile_manager.cpp
67 configuration/configure_profile_manager.h 67 configuration/configure_profile_manager.h
68 configuration/configure_profile_manager.ui 68 configuration/configure_profile_manager.ui
69 configuration/configure_service.cpp
70 configuration/configure_service.h
71 configuration/configure_service.ui
69 configuration/configure_system.cpp 72 configuration/configure_system.cpp
70 configuration/configure_system.h 73 configuration/configure_system.h
71 configuration/configure_system.ui 74 configuration/configure_system.ui
@@ -186,6 +189,10 @@ if (YUZU_USE_QT_WEB_ENGINE)
186 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) 189 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
187endif () 190endif ()
188 191
192if (YUZU_ENABLE_BOXCAT)
193 target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT)
194endif ()
195
189if(UNIX AND NOT APPLE) 196if(UNIX AND NOT APPLE)
190 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") 197 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
191endif() 198endif()
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 92d9fb161..4cb27ddb2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -525,6 +525,17 @@ void Config::ReadDebuggingValues() {
525 qt_config->endGroup(); 525 qt_config->endGroup();
526} 526}
527 527
528void Config::ReadServiceValues() {
529 qt_config->beginGroup(QStringLiteral("Services"));
530 Settings::values.bcat_backend =
531 ReadSetting(QStringLiteral("bcat_backend"), QStringLiteral("boxcat"))
532 .toString()
533 .toStdString();
534 Settings::values.bcat_boxcat_local =
535 ReadSetting(QStringLiteral("bcat_boxcat_local"), false).toBool();
536 qt_config->endGroup();
537}
538
528void Config::ReadDisabledAddOnValues() { 539void Config::ReadDisabledAddOnValues() {
529 const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns")); 540 const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
530 541
@@ -769,6 +780,7 @@ void Config::ReadValues() {
769 ReadMiscellaneousValues(); 780 ReadMiscellaneousValues();
770 ReadDebuggingValues(); 781 ReadDebuggingValues();
771 ReadWebServiceValues(); 782 ReadWebServiceValues();
783 ReadServiceValues();
772 ReadDisabledAddOnValues(); 784 ReadDisabledAddOnValues();
773 ReadUIValues(); 785 ReadUIValues();
774} 786}
@@ -866,6 +878,7 @@ void Config::SaveValues() {
866 SaveMiscellaneousValues(); 878 SaveMiscellaneousValues();
867 SaveDebuggingValues(); 879 SaveDebuggingValues();
868 SaveWebServiceValues(); 880 SaveWebServiceValues();
881 SaveServiceValues();
869 SaveDisabledAddOnValues(); 882 SaveDisabledAddOnValues();
870 SaveUIValues(); 883 SaveUIValues();
871} 884}
@@ -963,6 +976,14 @@ void Config::SaveDebuggingValues() {
963 qt_config->endGroup(); 976 qt_config->endGroup();
964} 977}
965 978
979void Config::SaveServiceValues() {
980 qt_config->beginGroup(QStringLiteral("Services"));
981 WriteSetting(QStringLiteral("bcat_backend"),
982 QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null"));
983 WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false);
984 qt_config->endGroup();
985}
986
966void Config::SaveDisabledAddOnValues() { 987void Config::SaveDisabledAddOnValues() {
967 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns")); 988 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
968 989
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 6b523ecdd..ba6888004 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,6 +42,7 @@ private:
42 void ReadCoreValues(); 42 void ReadCoreValues();
43 void ReadDataStorageValues(); 43 void ReadDataStorageValues();
44 void ReadDebuggingValues(); 44 void ReadDebuggingValues();
45 void ReadServiceValues();
45 void ReadDisabledAddOnValues(); 46 void ReadDisabledAddOnValues();
46 void ReadMiscellaneousValues(); 47 void ReadMiscellaneousValues();
47 void ReadPathValues(); 48 void ReadPathValues();
@@ -65,6 +66,7 @@ private:
65 void SaveCoreValues(); 66 void SaveCoreValues();
66 void SaveDataStorageValues(); 67 void SaveDataStorageValues();
67 void SaveDebuggingValues(); 68 void SaveDebuggingValues();
69 void SaveServiceValues();
68 void SaveDisabledAddOnValues(); 70 void SaveDisabledAddOnValues();
69 void SaveMiscellaneousValues(); 71 void SaveMiscellaneousValues();
70 void SavePathValues(); 72 void SavePathValues();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 49fadd0ef..372427ae2 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -98,6 +98,11 @@
98 <string>Web</string> 98 <string>Web</string>
99 </attribute> 99 </attribute>
100 </widget> 100 </widget>
101 <widget class="ConfigureService" name="serviceTab">
102 <attribute name="title">
103 <string>Services</string>
104 </attribute>
105 </widget>
101 </widget> 106 </widget>
102 </item> 107 </item>
103 </layout> 108 </layout>
@@ -178,6 +183,12 @@
178 <header>configuration/configure_hotkeys.h</header> 183 <header>configuration/configure_hotkeys.h</header>
179 <container>1</container> 184 <container>1</container>
180 </customwidget> 185 </customwidget>
186 <customwidget>
187 <class>ConfigureService</class>
188 <extends>QWidget</extends>
189 <header>configuration/configure_service.h</header>
190 <container>1</container>
191 </customwidget>
181 </customwidgets> 192 </customwidgets>
182 <resources/> 193 <resources/>
183 <connections> 194 <connections>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 7c875ae87..25b2e1b05 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -44,6 +44,7 @@ void ConfigureDialog::ApplyConfiguration() {
44 ui->audioTab->ApplyConfiguration(); 44 ui->audioTab->ApplyConfiguration();
45 ui->debugTab->ApplyConfiguration(); 45 ui->debugTab->ApplyConfiguration();
46 ui->webTab->ApplyConfiguration(); 46 ui->webTab->ApplyConfiguration();
47 ui->serviceTab->ApplyConfiguration();
47 Settings::Apply(); 48 Settings::Apply();
48 Settings::LogSettings(); 49 Settings::LogSettings();
49} 50}
@@ -74,7 +75,8 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
74void ConfigureDialog::PopulateSelectionList() { 75void ConfigureDialog::PopulateSelectionList() {
75 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ 76 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
76 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, 77 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
77 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}}, 78 {tr("System"),
79 {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}},
78 {tr("Graphics"), {ui->graphicsTab}}, 80 {tr("Graphics"), {ui->graphicsTab}},
79 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, 81 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
80 }; 82 };
@@ -108,6 +110,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
108 {ui->webTab, tr("Web")}, 110 {ui->webTab, tr("Web")},
109 {ui->gameListTab, tr("Game List")}, 111 {ui->gameListTab, tr("Game List")},
110 {ui->filesystemTab, tr("Filesystem")}, 112 {ui->filesystemTab, tr("Filesystem")},
113 {ui->serviceTab, tr("Services")},
111 }; 114 };
112 115
113 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); 116 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
new file mode 100644
index 000000000..06566e981
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -0,0 +1,138 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QGraphicsItem>
6#include <QtConcurrent/QtConcurrent>
7#include "core/hle/service/bcat/backend/boxcat.h"
8#include "core/settings.h"
9#include "ui_configure_service.h"
10#include "yuzu/configuration/configure_service.h"
11
12namespace {
13QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
14 QString out;
15
16 if (status.header.has_value()) {
17 out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header));
18 }
19
20 if (status.events.size() == 1) {
21 out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front()));
22 } else {
23 for (const auto& event : status.events) {
24 out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event));
25 }
26 }
27
28 if (status.footer.has_value()) {
29 out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer));
30 }
31
32 return out;
33}
34} // Anonymous namespace
35
36ConfigureService::ConfigureService(QWidget* parent)
37 : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
38 ui->setupUi(this);
39
40 ui->bcat_source->addItem(QStringLiteral("None"));
41 ui->bcat_empty_label->setHidden(true);
42 ui->bcat_empty_header->setHidden(true);
43
44#ifdef YUZU_ENABLE_BOXCAT
45 ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
46#endif
47
48 connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
49 &ConfigureService::OnBCATImplChanged);
50
51 this->SetConfiguration();
52}
53
54ConfigureService::~ConfigureService() = default;
55
56void ConfigureService::ApplyConfiguration() {
57 Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
58}
59
60void ConfigureService::RetranslateUi() {
61 ui->retranslateUi(this);
62}
63
64void ConfigureService::SetConfiguration() {
65 const int index =
66 ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
67 ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
68}
69
70std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
71 std::optional<std::string> global;
72 std::map<std::string, Service::BCAT::EventStatus> map;
73 const auto res = Service::BCAT::Boxcat::GetStatus(global, map);
74
75 switch (res) {
76 case Service::BCAT::Boxcat::StatusResult::Success:
77 break;
78 case Service::BCAT::Boxcat::StatusResult::Offline:
79 return {QString{},
80 tr("The boxcat service is offline or you are not connected to the internet.")};
81 case Service::BCAT::Boxcat::StatusResult::ParseError:
82 return {QString{},
83 tr("There was an error while processing the boxcat event data. Contact the yuzu "
84 "developers.")};
85 case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
86 return {QString{},
87 tr("The version of yuzu you are using is either too new or too old for the server. "
88 "Try updating to the latest official release of yuzu.")};
89 }
90
91 if (map.empty()) {
92 return {QStringLiteral("Current Boxcat Events"),
93 tr("There are currently no events on boxcat.")};
94 }
95
96 QString out;
97
98 if (global.has_value()) {
99 out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global));
100 }
101
102 for (const auto& [key, value] : map) {
103 out += QStringLiteral("%1<b>%2</b><br>%3")
104 .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
105 .arg(QString::fromStdString(key))
106 .arg(FormatEventStatusString(value));
107 }
108 return {QStringLiteral("Current Boxcat Events"), std::move(out)};
109}
110
111void ConfigureService::OnBCATImplChanged() {
112#ifdef YUZU_ENABLE_BOXCAT
113 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
114 ui->bcat_empty_header->setHidden(!boxcat);
115 ui->bcat_empty_label->setHidden(!boxcat);
116 ui->bcat_empty_header->setText(QString{});
117 ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
118
119 if (!boxcat)
120 return;
121
122 const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); });
123
124 watcher.setFuture(future);
125 connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this,
126 [this] { OnUpdateBCATEmptyLabel(watcher.result()); });
127#endif
128}
129
130void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
131#ifdef YUZU_ENABLE_BOXCAT
132 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
133 if (boxcat) {
134 ui->bcat_empty_header->setText(string.first);
135 ui->bcat_empty_label->setText(string.second);
136 }
137#endif
138}
diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_service.h
new file mode 100644
index 000000000..f5c1b703a
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.h
@@ -0,0 +1,34 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QFutureWatcher>
9#include <QWidget>
10
11namespace Ui {
12class ConfigureService;
13}
14
15class ConfigureService : public QWidget {
16 Q_OBJECT
17
18public:
19 explicit ConfigureService(QWidget* parent = nullptr);
20 ~ConfigureService() override;
21
22 void ApplyConfiguration();
23 void RetranslateUi();
24
25private:
26 void SetConfiguration();
27
28 std::pair<QString, QString> BCATDownloadEvents();
29 void OnBCATImplChanged();
30 void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
31
32 std::unique_ptr<Ui::ConfigureService> ui;
33 QFutureWatcher<std::pair<QString, QString>> watcher{this};
34};
diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui
new file mode 100644
index 000000000..9668dd557
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.ui
@@ -0,0 +1,124 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureService</class>
4 <widget class="QWidget" name="ConfigureService">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>433</width>
10 <height>561</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBox">
21 <property name="title">
22 <string>BCAT</string>
23 </property>
24 <layout class="QGridLayout" name="gridLayout">
25 <item row="1" column="1" colspan="2">
26 <widget class="QLabel" name="label_2">
27 <property name="maximumSize">
28 <size>
29 <width>260</width>
30 <height>16777215</height>
31 </size>
32 </property>
33 <property name="text">
34 <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
35 </property>
36 <property name="wordWrap">
37 <bool>true</bool>
38 </property>
39 </widget>
40 </item>
41 <item row="0" column="0">
42 <widget class="QLabel" name="label">
43 <property name="maximumSize">
44 <size>
45 <width>16777215</width>
46 <height>16777215</height>
47 </size>
48 </property>
49 <property name="text">
50 <string>BCAT Backend</string>
51 </property>
52 </widget>
53 </item>
54 <item row="3" column="1" colspan="2">
55 <widget class="QLabel" name="bcat_empty_label">
56 <property name="enabled">
57 <bool>true</bool>
58 </property>
59 <property name="maximumSize">
60 <size>
61 <width>260</width>
62 <height>16777215</height>
63 </size>
64 </property>
65 <property name="text">
66 <string/>
67 </property>
68 <property name="alignment">
69 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
70 </property>
71 <property name="wordWrap">
72 <bool>true</bool>
73 </property>
74 </widget>
75 </item>
76 <item row="2" column="1" colspan="2">
77 <widget class="QLabel" name="label_3">
78 <property name="text">
79 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
80 </property>
81 <property name="openExternalLinks">
82 <bool>true</bool>
83 </property>
84 </widget>
85 </item>
86 <item row="0" column="1" colspan="2">
87 <widget class="QComboBox" name="bcat_source"/>
88 </item>
89 <item row="3" column="0">
90 <widget class="QLabel" name="bcat_empty_header">
91 <property name="text">
92 <string/>
93 </property>
94 <property name="alignment">
95 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
96 </property>
97 <property name="wordWrap">
98 <bool>true</bool>
99 </property>
100 </widget>
101 </item>
102 </layout>
103 </widget>
104 </item>
105 </layout>
106 </item>
107 <item>
108 <spacer name="verticalSpacer">
109 <property name="orientation">
110 <enum>Qt::Vertical</enum>
111 </property>
112 <property name="sizeHint" stdset="0">
113 <size>
114 <width>20</width>
115 <height>40</height>
116 </size>
117 </property>
118 </spacer>
119 </item>
120 </layout>
121 </widget>
122 <resources/>
123 <connections/>
124</ui>
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d5fab2f1f..a2b88c787 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -172,9 +172,7 @@ void GameList::onTextChanged(const QString& new_text) {
172 const int folder_count = tree_view->model()->rowCount(); 172 const int folder_count = tree_view->model()->rowCount();
173 QString edit_filter_text = new_text.toLower(); 173 QString edit_filter_text = new_text.toLower();
174 QStandardItem* folder; 174 QStandardItem* folder;
175 QStandardItem* child;
176 int children_total = 0; 175 int children_total = 0;
177 QModelIndex root_index = item_model->invisibleRootItem()->index();
178 176
179 // If the searchfield is empty every item is visible 177 // If the searchfield is empty every item is visible
180 // Otherwise the filter gets applied 178 // Otherwise the filter gets applied
@@ -272,6 +270,8 @@ void GameList::onUpdateThemedIcons() {
272 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 270 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
273 Qt::DecorationRole); 271 Qt::DecorationRole);
274 break; 272 break;
273 default:
274 break;
275 } 275 }
276 } 276 }
277} 277}
@@ -392,6 +392,8 @@ void GameList::ValidateEntry(const QModelIndex& item) {
392 case GameListItemType::AddDir: 392 case GameListItemType::AddDir:
393 emit AddDirectory(); 393 emit AddDirectory();
394 break; 394 break;
395 default:
396 break;
395 } 397 }
396} 398}
397 399
@@ -462,6 +464,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
462 case GameListItemType::SysNandDir: 464 case GameListItemType::SysNandDir:
463 AddPermDirPopup(context_menu, selected); 465 AddPermDirPopup(context_menu, selected);
464 break; 466 break;
467 default:
468 break;
465 } 469 }
466 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); 470 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
467} 471}
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index a8d888fee..1c2b37afd 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -247,7 +247,7 @@ public:
247 Qt::DecorationRole); 247 Qt::DecorationRole);
248 setData(QObject::tr("System Titles"), Qt::DisplayRole); 248 setData(QObject::tr("System Titles"), Qt::DisplayRole);
249 break; 249 break;
250 case GameListItemType::CustomDir: 250 case GameListItemType::CustomDir: {
251 const QString icon_name = QFileInfo::exists(game_dir->path) 251 const QString icon_name = QFileInfo::exists(game_dir->path)
252 ? QStringLiteral("folder") 252 ? QStringLiteral("folder")
253 : QStringLiteral("bad_folder"); 253 : QStringLiteral("bad_folder");
@@ -256,8 +256,11 @@ public:
256 Qt::DecorationRole); 256 Qt::DecorationRole);
257 setData(game_dir->path, Qt::DisplayRole); 257 setData(game_dir->path, Qt::DisplayRole);
258 break; 258 break;
259 }; 259 }
260 }; 260 default:
261 break;
262 }
263 }
261 264
262 int type() const override { 265 int type() const override {
263 return static_cast<int>(dir_type); 266 return static_cast<int>(dir_type);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index fd21a9761..4c81ef12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -326,10 +326,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
326 } 326 }
327 } else { 327 } else {
328 std::vector<u8> icon; 328 std::vector<u8> icon;
329 const auto res1 = loader->ReadIcon(icon); 329 [[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
330 330
331 std::string name = " "; 331 std::string name = " ";
332 const auto res3 = loader->ReadTitle(name); 332 [[maybe_unused]] const auto res3 = loader->ReadTitle(name);
333 333
334 const FileSys::PatchManager patch{program_id}; 334 const FileSys::PatchManager patch{program_id};
335 335
@@ -354,20 +354,20 @@ void GameListWorker::run() {
354 for (UISettings::GameDir& game_dir : game_dirs) { 354 for (UISettings::GameDir& game_dir : game_dirs) {
355 if (game_dir.path == QStringLiteral("SDMC")) { 355 if (game_dir.path == QStringLiteral("SDMC")) {
356 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); 356 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
357 emit DirEntryReady({game_list_dir}); 357 emit DirEntryReady(game_list_dir);
358 AddTitlesToGameList(game_list_dir); 358 AddTitlesToGameList(game_list_dir);
359 } else if (game_dir.path == QStringLiteral("UserNAND")) { 359 } else if (game_dir.path == QStringLiteral("UserNAND")) {
360 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); 360 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
361 emit DirEntryReady({game_list_dir}); 361 emit DirEntryReady(game_list_dir);
362 AddTitlesToGameList(game_list_dir); 362 AddTitlesToGameList(game_list_dir);
363 } else if (game_dir.path == QStringLiteral("SysNAND")) { 363 } else if (game_dir.path == QStringLiteral("SysNAND")) {
364 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); 364 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
365 emit DirEntryReady({game_list_dir}); 365 emit DirEntryReady(game_list_dir);
366 AddTitlesToGameList(game_list_dir); 366 AddTitlesToGameList(game_list_dir);
367 } else { 367 } else {
368 watch_list.append(game_dir.path); 368 watch_list.append(game_dir.path);
369 auto* const game_list_dir = new GameListDir(game_dir); 369 auto* const game_list_dir = new GameListDir(game_dir);
370 emit DirEntryReady({game_list_dir}); 370 emit DirEntryReady(game_list_dir);
371 provider->ClearAllEntries(); 371 provider->ClearAllEntries();
372 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2, 372 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
373 game_list_dir); 373 game_list_dir);
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 6e52fca89..84e4e1b42 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -75,8 +75,9 @@ private:
75 75
76 std::shared_ptr<FileSys::VfsFilesystem> vfs; 76 std::shared_ptr<FileSys::VfsFilesystem> vfs;
77 FileSys::ManualContentProvider* provider; 77 FileSys::ManualContentProvider* provider;
78 QStringList watch_list;
79 const CompatibilityList& compatibility_list;
80 QVector<UISettings::GameDir>& game_dirs; 78 QVector<UISettings::GameDir>& game_dirs;
79 const CompatibilityList& compatibility_list;
80
81 QStringList watch_list;
81 std::atomic_bool stop_processing; 82 std::atomic_bool stop_processing;
82}; 83};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2d82df739..451e4a4d6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1889,15 +1889,24 @@ void GMainWindow::OnCaptureScreenshot() {
1889} 1889}
1890 1890
1891void GMainWindow::UpdateWindowTitle(const QString& title_name) { 1891void GMainWindow::UpdateWindowTitle(const QString& title_name) {
1892 const QString full_name = QString::fromUtf8(Common::g_build_fullname); 1892 const auto full_name = std::string(Common::g_build_fullname);
1893 const QString branch_name = QString::fromUtf8(Common::g_scm_branch); 1893 const auto branch_name = std::string(Common::g_scm_branch);
1894 const QString description = QString::fromUtf8(Common::g_scm_desc); 1894 const auto description = std::string(Common::g_scm_desc);
1895 const auto build_id = std::string(Common::g_build_id);
1896
1897 const auto date =
1898 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();
1895 1899
1896 if (title_name.isEmpty()) { 1900 if (title_name.isEmpty()) {
1897 setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description)); 1901 const auto fmt = std::string(Common::g_title_bar_format_idle);
1902 setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt,
1903 full_name, branch_name, description,
1904 std::string{}, date, build_id)));
1898 } else { 1905 } else {
1899 setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3") 1906 const auto fmt = std::string(Common::g_title_bar_format_running);
1900 .arg(full_name, branch_name, description, title_name)); 1907 setWindowTitle(QString::fromStdString(
1908 fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name,
1909 description, title_name.toStdString(), date, build_id)));
1901 } 1910 }
1902} 1911}
1903 1912
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 7f7d247a3..43bad9678 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -9,6 +9,8 @@ namespace UISettings {
9const Themes themes{{ 9const Themes themes{{
10 {"Default", "default"}, 10 {"Default", "default"},
11 {"Dark", "qdarkstyle"}, 11 {"Dark", "qdarkstyle"},
12 {"Colorful", "colorful"},
13 {"Colorful Dark", "colorful_dark"},
12}}; 14}};
13 15
14Values values = {}; 16Values values = {};
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index c57290006..a8eaf5f8c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -24,7 +24,7 @@ struct Shortcut {
24 ContextualShortcut shortcut; 24 ContextualShortcut shortcut;
25}; 25};
26 26
27using Themes = std::array<std::pair<const char*, const char*>, 2>; 27using Themes = std::array<std::pair<const char*, const char*>, 4>;
28extern const Themes themes; 28extern const Themes themes;
29 29
30struct GameDir { 30struct GameDir {
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index d82438502..1a812cb87 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -433,6 +433,11 @@ void Config::ReadValues() {
433 sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); 433 sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
434 Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); 434 Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
435 Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); 435 Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
436
437 // Services
438 Settings::values.bcat_backend = sdl2_config->Get("Services", "bcat_backend", "boxcat");
439 Settings::values.bcat_boxcat_local =
440 sdl2_config->GetBoolean("Services", "bcat_boxcat_local", false);
436} 441}
437 442
438void Config::Reload() { 443void Config::Reload() {
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index a6171c3ed..8d18a4a5a 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -251,6 +251,11 @@ web_api_url = https://api.yuzu-emu.org
251yuzu_username = 251yuzu_username =
252yuzu_token = 252yuzu_token =
253 253
254[Services]
255# The name of the backend to use for BCAT
256# If this is set to 'boxcat' boxcat will be used, otherwise a null implementation will be used
257bcat_backend =
258
254[AddOns] 259[AddOns]
255# Used to disable add-ons 260# Used to disable add-ons
256# List of title IDs of games that will have add-ons disabled (separated by '|'): 261# List of title IDs of games that will have add-ons disabled (separated by '|'):
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index a6edc089a..b1c512db1 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -4,6 +4,9 @@
4 4
5#include <SDL.h> 5#include <SDL.h>
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "common/scm_rev.h"
8#include "core/core.h"
9#include "core/perf_stats.h"
7#include "input_common/keyboard.h" 10#include "input_common/keyboard.h"
8#include "input_common/main.h" 11#include "input_common/main.h"
9#include "input_common/motion_emu.h" 12#include "input_common/motion_emu.h"
@@ -170,6 +173,16 @@ void EmuWindow_SDL2::PollEvents() {
170 break; 173 break;
171 } 174 }
172 } 175 }
176
177 const u32 current_time = SDL_GetTicks();
178 if (current_time > last_time + 2000) {
179 const auto results = Core::System::GetInstance().GetAndResetPerfStats();
180 const auto title = fmt::format(
181 "yuzu {} | {}-{} | FPS: {:.0f} ({:.0%})", Common::g_build_fullname,
182 Common::g_scm_branch, Common::g_scm_desc, results.game_fps, results.emulation_speed);
183 SDL_SetWindowTitle(render_window, title.c_str());
184 last_time = current_time;
185 }
173} 186}
174 187
175void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { 188void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index d8051ebdf..eaa971f77 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -60,4 +60,7 @@ protected:
60 60
61 /// Internal SDL2 render window 61 /// Internal SDL2 render window
62 SDL_Window* render_window; 62 SDL_Window* render_window;
63
64 /// Keeps track of how often to update the title bar during gameplay
65 u32 last_time = 0;
63}; 66};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index bac05b959..3ee088a91 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -186,8 +186,6 @@ int main(int argc, char** argv) {
186 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 186 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
187 system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); 187 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
188 188
189 SCOPE_EXIT({ system.Shutdown(); });
190
191 const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; 189 const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
192 190
193 switch (load_result) { 191 switch (load_result) {
@@ -227,6 +225,8 @@ int main(int argc, char** argv) {
227 system.RunLoop(); 225 system.RunLoop();
228 } 226 }
229 227
228 system.Shutdown();
229
230 detached_tasks.WaitForAllTasks(); 230 detached_tasks.WaitForAllTasks();
231 return 0; 231 return 0;
232} 232}
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index 9a11dc6c3..84ab4d687 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -93,7 +93,6 @@ void Config::ReadValues() {
93 93
94 // System 94 // System
95 Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); 95 Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
96 const auto size = sdl2_config->GetInteger("System", "users_size", 0);
97 96
98 Settings::values.current_user = std::clamp<int>( 97 Settings::values.current_user = std::clamp<int>(
99 sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); 98 sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);