summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt18
-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.txt22
-rw-r--r--src/core/core.cpp29
-rw-r--r--src/core/core.h19
-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/es/es.cpp12
-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/lm/lm.cpp187
-rw-r--r--src/core/hle/service/lm/lm.h6
-rw-r--r--src/core/hle/service/lm/manager.cpp133
-rw-r--r--src/core/hle/service/lm/manager.h106
-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.cpp4
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/memory.cpp10
-rw-r--r--src/core/reporter.cpp51
-rw-r--r--src/core/reporter.h14
-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/maxwell_3d.cpp5
-rw-r--r--src/video_core/engines/maxwell_3d.h6
-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_rasterizer.cpp4
-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.cpp25
-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_general.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.ui7
-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.cpp42
-rw-r--r--src/yuzu/main.h3
-rw-r--r--src/yuzu/uisettings.cpp2
-rw-r--r--src/yuzu/uisettings.h3
-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
128 files changed, 5306 insertions, 742 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index dfed8b51d..906c486fd 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -15,11 +15,23 @@ if (DEFINED ENV{CI})
15 set(BUILD_TAG $ENV{AZURE_REPO_TAG}) 15 set(BUILD_TAG $ENV{AZURE_REPO_TAG})
16 endif() 16 endif()
17endif() 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 ()
18add_custom_command(OUTPUT scm_rev.cpp 27add_custom_command(OUTPUT scm_rev.cpp
19 COMMAND ${CMAKE_COMMAND} 28 COMMAND ${CMAKE_COMMAND}
20 -DSRC_DIR="${CMAKE_SOURCE_DIR}" 29 -DSRC_DIR="${CMAKE_SOURCE_DIR}"
21 -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}"
22 -DBUILD_TAG="${BUILD_TAG}" 33 -DBUILD_TAG="${BUILD_TAG}"
34 -DBUILD_ID="${DISPLAY_VERSION}"
23 -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" 35 -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
24 DEPENDS 36 DEPENDS
25 # 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,
@@ -60,9 +72,15 @@ add_custom_command(OUTPUT scm_rev.cpp
60 "${VIDEO_CORE}/shader/decode/video.cpp" 72 "${VIDEO_CORE}/shader/decode/video.cpp"
61 "${VIDEO_CORE}/shader/decode/warp.cpp" 73 "${VIDEO_CORE}/shader/decode/warp.cpp"
62 "${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"
63 "${VIDEO_CORE}/shader/control_flow.cpp" 77 "${VIDEO_CORE}/shader/control_flow.cpp"
64 "${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"
65 "${VIDEO_CORE}/shader/decode.cpp" 81 "${VIDEO_CORE}/shader/decode.cpp"
82 "${VIDEO_CORE}/shader/expr.cpp"
83 "${VIDEO_CORE}/shader/expr.h"
66 "${VIDEO_CORE}/shader/node.h" 84 "${VIDEO_CORE}/shader/node.h"
67 "${VIDEO_CORE}/shader/node_helper.cpp" 85 "${VIDEO_CORE}/shader/node_helper.cpp"
68 "${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 a6b56c9c6..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
@@ -324,6 +335,8 @@ add_library(core STATIC
324 hle/service/ldr/ldr.h 335 hle/service/ldr/ldr.h
325 hle/service/lm/lm.cpp 336 hle/service/lm/lm.cpp
326 hle/service/lm/lm.h 337 hle/service/lm/lm.h
338 hle/service/lm/manager.cpp
339 hle/service/lm/manager.h
327 hle/service/mig/mig.cpp 340 hle/service/mig/mig.cpp
328 hle/service/mig/mig.h 341 hle/service/mig/mig.h
329 hle/service/mii/mii.cpp 342 hle/service/mii/mii.cpp
@@ -499,6 +512,15 @@ create_target_directory_groups(core)
499 512
500target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 513target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
501target_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
502if (ENABLE_WEB_SERVICE) 524if (ENABLE_WEB_SERVICE)
503 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) 525 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
504 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 92ba42fb9..4d0ac72a5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -35,6 +35,7 @@
35#include "core/hle/service/apm/controller.h" 35#include "core/hle/service/apm/controller.h"
36#include "core/hle/service/filesystem/filesystem.h" 36#include "core/hle/service/filesystem/filesystem.h"
37#include "core/hle/service/glue/manager.h" 37#include "core/hle/service/glue/manager.h"
38#include "core/hle/service/lm/manager.h"
38#include "core/hle/service/service.h" 39#include "core/hle/service/service.h"
39#include "core/hle/service/sm/sm.h" 40#include "core/hle/service/sm/sm.h"
40#include "core/loader/loader.h" 41#include "core/loader/loader.h"
@@ -111,7 +112,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
111} 112}
112struct System::Impl { 113struct System::Impl {
113 explicit Impl(System& system) 114 explicit Impl(System& system)
114 : 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} {}
115 117
116 Cpu& CurrentCpuCore() { 118 Cpu& CurrentCpuCore() {
117 return cpu_core_manager.GetCurrentCore(); 119 return cpu_core_manager.GetCurrentCore();
@@ -249,6 +251,8 @@ struct System::Impl {
249 telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", 251 telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
250 perf_stats->GetMeanFrametime()); 252 perf_stats->GetMeanFrametime());
251 253
254 lm_manager.Flush();
255
252 is_powered_on = false; 256 is_powered_on = false;
253 exit_lock = false; 257 exit_lock = false;
254 258
@@ -337,8 +341,10 @@ struct System::Impl {
337 bool is_powered_on = false; 341 bool is_powered_on = false;
338 bool exit_lock = false; 342 bool exit_lock = false;
339 343
344 Reporter reporter;
340 std::unique_ptr<Memory::CheatEngine> cheat_engine; 345 std::unique_ptr<Memory::CheatEngine> cheat_engine;
341 std::unique_ptr<Tools::Freezer> memory_freezer; 346 std::unique_ptr<Tools::Freezer> memory_freezer;
347 std::array<u8, 0x20> build_id{};
342 348
343 /// Frontend applets 349 /// Frontend applets
344 Service::AM::Applets::AppletManager applet_manager; 350 Service::AM::Applets::AppletManager applet_manager;
@@ -346,8 +352,9 @@ struct System::Impl {
346 /// APM (Performance) services 352 /// APM (Performance) services
347 Service::APM::Controller apm_controller{core_timing}; 353 Service::APM::Controller apm_controller{core_timing};
348 354
349 /// Glue services 355 /// Service State
350 Service::Glue::ARPManager arp_manager; 356 Service::Glue::ARPManager arp_manager;
357 Service::LM::Manager lm_manager{reporter};
351 358
352 /// Service manager 359 /// Service manager
353 std::shared_ptr<Service::SM::ServiceManager> service_manager; 360 std::shared_ptr<Service::SM::ServiceManager> service_manager;
@@ -355,8 +362,6 @@ struct System::Impl {
355 /// Telemetry session for this emulation session 362 /// Telemetry session for this emulation session
356 std::unique_ptr<Core::TelemetrySession> telemetry_session; 363 std::unique_ptr<Core::TelemetrySession> telemetry_session;
357 364
358 Reporter reporter;
359
360 ResultStatus status = ResultStatus::Success; 365 ResultStatus status = ResultStatus::Success;
361 std::string status_details = ""; 366 std::string status_details = "";
362 367
@@ -632,6 +637,14 @@ const Service::APM::Controller& System::GetAPMController() const {
632 return impl->apm_controller; 637 return impl->apm_controller;
633} 638}
634 639
640Service::LM::Manager& System::GetLogManager() {
641 return impl->lm_manager;
642}
643
644const Service::LM::Manager& System::GetLogManager() const {
645 return impl->lm_manager;
646}
647
635void System::SetExitLock(bool locked) { 648void System::SetExitLock(bool locked) {
636 impl->exit_lock = locked; 649 impl->exit_lock = locked;
637} 650}
@@ -640,6 +653,14 @@ bool System::GetExitLock() const {
640 return impl->exit_lock; 653 return impl->exit_lock;
641} 654}
642 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
643System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { 664System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
644 return impl->Init(*this, emu_window); 665 return impl->Init(*this, emu_window);
645} 666}
diff --git a/src/core/core.h b/src/core/core.h
index ff10ebe12..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"
@@ -58,6 +57,10 @@ namespace Glue {
58class ARPManager; 57class ARPManager;
59} 58}
60 59
60namespace LM {
61class Manager;
62} // namespace LM
63
61namespace SM { 64namespace SM {
62class ServiceManager; 65class ServiceManager;
63} // namespace SM 66} // namespace SM
@@ -98,6 +101,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
98 101
99class System { 102class System {
100public: 103public:
104 using CurrentBuildProcessID = std::array<u8, 0x20>;
105
101 System(const System&) = delete; 106 System(const System&) = delete;
102 System& operator=(const System&) = delete; 107 System& operator=(const System&) = delete;
103 108
@@ -326,10 +331,18 @@ public:
326 331
327 const Service::APM::Controller& GetAPMController() const; 332 const Service::APM::Controller& GetAPMController() const;
328 333
334 Service::LM::Manager& GetLogManager();
335
336 const Service::LM::Manager& GetLogManager() const;
337
329 void SetExitLock(bool locked); 338 void SetExitLock(bool locked);
330 339
331 bool GetExitLock() const; 340 bool GetExitLock() const;
332 341
342 void SetCurrentProcessBuildID(const CurrentBuildProcessID& id);
343
344 const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
345
333private: 346private:
334 System(); 347 System();
335 348
@@ -353,8 +366,4 @@ private:
353 static System s_instance; 366 static System s_instance;
354}; 367};
355 368
356inline Kernel::Process* CurrentProcess() {
357 return System::GetInstance().CurrentProcess();
358}
359
360} // 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/es/es.cpp b/src/core/hle/service/es/es.cpp
index af70d174d..f77ddd739 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -128,7 +128,7 @@ private:
128 void CountCommonTicket(Kernel::HLERequestContext& ctx) { 128 void CountCommonTicket(Kernel::HLERequestContext& ctx) {
129 LOG_DEBUG(Service_ETicket, "called"); 129 LOG_DEBUG(Service_ETicket, "called");
130 130
131 const auto count = keys.GetCommonTickets().size(); 131 const u32 count = static_cast<u32>(keys.GetCommonTickets().size());
132 132
133 IPC::ResponseBuilder rb{ctx, 3}; 133 IPC::ResponseBuilder rb{ctx, 3};
134 rb.Push(RESULT_SUCCESS); 134 rb.Push(RESULT_SUCCESS);
@@ -138,7 +138,7 @@ private:
138 void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { 138 void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
139 LOG_DEBUG(Service_ETicket, "called"); 139 LOG_DEBUG(Service_ETicket, "called");
140 140
141 const auto count = keys.GetPersonalizedTickets().size(); 141 const u32 count = static_cast<u32>(keys.GetPersonalizedTickets().size());
142 142
143 IPC::ResponseBuilder rb{ctx, 3}; 143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(RESULT_SUCCESS); 144 rb.Push(RESULT_SUCCESS);
@@ -150,7 +150,7 @@ private:
150 if (keys.GetCommonTickets().empty()) 150 if (keys.GetCommonTickets().empty())
151 out_entries = 0; 151 out_entries = 0;
152 else 152 else
153 out_entries = ctx.GetWriteBufferSize() / sizeof(u128); 153 out_entries = static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(u128));
154 154
155 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); 155 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
156 156
@@ -160,7 +160,7 @@ private:
160 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), 160 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
161 [](const auto& pair) { return pair.first; }); 161 [](const auto& pair) { return pair.first; });
162 162
163 out_entries = std::min<u32>(ids.size(), out_entries); 163 out_entries = static_cast<u32>(std::min<std::size_t>(ids.size(), out_entries));
164 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); 164 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
165 165
166 IPC::ResponseBuilder rb{ctx, 3}; 166 IPC::ResponseBuilder rb{ctx, 3};
@@ -173,7 +173,7 @@ private:
173 if (keys.GetPersonalizedTickets().empty()) 173 if (keys.GetPersonalizedTickets().empty())
174 out_entries = 0; 174 out_entries = 0;
175 else 175 else
176 out_entries = ctx.GetWriteBufferSize() / sizeof(u128); 176 out_entries = static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(u128));
177 177
178 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); 178 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
179 179
@@ -183,7 +183,7 @@ private:
183 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), 183 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
184 [](const auto& pair) { return pair.first; }); 184 [](const auto& pair) { return pair.first; });
185 185
186 out_entries = std::min<u32>(ids.size(), out_entries); 186 out_entries = static_cast<u32>(std::min<std::size_t>(ids.size(), out_entries));
187 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); 187 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
188 188
189 IPC::ResponseBuilder rb{ctx, 3}; 189 IPC::ResponseBuilder rb{ctx, 3};
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/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index 2a61593e2..435f2d286 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -6,8 +6,10 @@
6#include <string> 6#include <string>
7 7
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "common/scope_exit.h"
9#include "core/hle/ipc_helpers.h" 10#include "core/hle/ipc_helpers.h"
10#include "core/hle/service/lm/lm.h" 11#include "core/hle/service/lm/lm.h"
12#include "core/hle/service/lm/manager.h"
11#include "core/hle/service/service.h" 13#include "core/hle/service/service.h"
12#include "core/memory.h" 14#include "core/memory.h"
13 15
@@ -15,65 +17,16 @@ namespace Service::LM {
15 17
16class ILogger final : public ServiceFramework<ILogger> { 18class ILogger final : public ServiceFramework<ILogger> {
17public: 19public:
18 ILogger() : ServiceFramework("ILogger") { 20 ILogger(Manager& manager) : ServiceFramework("ILogger"), manager(manager) {
19 static const FunctionInfo functions[] = { 21 static const FunctionInfo functions[] = {
20 {0x00000000, &ILogger::Initialize, "Initialize"}, 22 {0, &ILogger::Log, "Log"},
21 {0x00000001, &ILogger::SetDestination, "SetDestination"}, 23 {1, &ILogger::SetDestination, "SetDestination"},
22 }; 24 };
23 RegisterHandlers(functions); 25 RegisterHandlers(functions);
24 } 26 }
25 27
26private: 28private:
27 struct MessageHeader { 29 void Log(Kernel::HLERequestContext& ctx) {
28 enum Flags : u32_le {
29 IsHead = 1,
30 IsTail = 2,
31 };
32 enum Severity : u32_le {
33 Trace,
34 Info,
35 Warning,
36 Error,
37 Critical,
38 };
39
40 u64_le pid;
41 u64_le threadContext;
42 union {
43 BitField<0, 16, Flags> flags;
44 BitField<16, 8, Severity> severity;
45 BitField<24, 8, u32> verbosity;
46 };
47 u32_le payload_size;
48
49 bool IsHeadLog() const {
50 return flags & Flags::IsHead;
51 }
52 bool IsTailLog() const {
53 return flags & Flags::IsTail;
54 }
55 };
56 static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
57
58 /// Log field type
59 enum class Field : u8 {
60 Skip = 1,
61 Message = 2,
62 Line = 3,
63 Filename = 4,
64 Function = 5,
65 Module = 6,
66 Thread = 7,
67 };
68
69 /**
70 * ILogger::Initialize service function
71 * Inputs:
72 * 0: 0x00000000
73 * Outputs:
74 * 0: ResultCode
75 */
76 void Initialize(Kernel::HLERequestContext& ctx) {
77 // This function only succeeds - Get that out of the way 30 // This function only succeeds - Get that out of the way
78 IPC::ResponseBuilder rb{ctx, 2}; 31 IPC::ResponseBuilder rb{ctx, 2};
79 rb.Push(RESULT_SUCCESS); 32 rb.Push(RESULT_SUCCESS);
@@ -85,140 +38,70 @@ private:
85 Memory::ReadBlock(addr, &header, sizeof(MessageHeader)); 38 Memory::ReadBlock(addr, &header, sizeof(MessageHeader));
86 addr += sizeof(MessageHeader); 39 addr += sizeof(MessageHeader);
87 40
88 if (header.IsHeadLog()) { 41 FieldMap fields;
89 log_stream.str("");
90 log_stream.clear();
91 }
92
93 // Parse out log metadata
94 u32 line{};
95 std::string module;
96 std::string message;
97 std::string filename;
98 std::string function;
99 std::string thread;
100 while (addr < end_addr) { 42 while (addr < end_addr) {
101 const Field field{static_cast<Field>(Memory::Read8(addr++))}; 43 const auto field = static_cast<Field>(Memory::Read8(addr++));
102 const std::size_t length{Memory::Read8(addr++)}; 44 const auto length = Memory::Read8(addr++);
103 45
104 if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) { 46 if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) {
105 ++addr; 47 ++addr;
106 } 48 }
107 49
108 switch (field) { 50 SCOPE_EXIT({ addr += length; });
109 case Field::Skip:
110 break;
111 case Field::Message:
112 message = Memory::ReadCString(addr, length);
113 break;
114 case Field::Line:
115 line = Memory::Read32(addr);
116 break;
117 case Field::Filename:
118 filename = Memory::ReadCString(addr, length);
119 break;
120 case Field::Function:
121 function = Memory::ReadCString(addr, length);
122 break;
123 case Field::Module:
124 module = Memory::ReadCString(addr, length);
125 break;
126 case Field::Thread:
127 thread = Memory::ReadCString(addr, length);
128 break;
129 }
130 51
131 addr += length; 52 if (field == Field::Skip) {
132 } 53 continue;
54 }
133 55
134 // Empty log - nothing to do here 56 std::vector<u8> data(length);
135 if (log_stream.str().empty() && message.empty()) { 57 Memory::ReadBlock(addr, data.data(), length);
136 return; 58 fields.emplace(field, std::move(data));
137 } 59 }
138 60
139 // Format a nicely printable string out of the log metadata 61 manager.Log({header, std::move(fields)});
140 if (!filename.empty()) {
141 log_stream << filename << ':';
142 }
143 if (!module.empty()) {
144 log_stream << module << ':';
145 }
146 if (!function.empty()) {
147 log_stream << function << ':';
148 }
149 if (line) {
150 log_stream << std::to_string(line) << ':';
151 }
152 if (!thread.empty()) {
153 log_stream << thread << ':';
154 }
155 if (log_stream.str().length() > 0 && log_stream.str().back() == ':') {
156 log_stream << ' ';
157 }
158 log_stream << message;
159
160 if (header.IsTailLog()) {
161 switch (header.severity) {
162 case MessageHeader::Severity::Trace:
163 LOG_DEBUG(Debug_Emulated, "{}", log_stream.str());
164 break;
165 case MessageHeader::Severity::Info:
166 LOG_INFO(Debug_Emulated, "{}", log_stream.str());
167 break;
168 case MessageHeader::Severity::Warning:
169 LOG_WARNING(Debug_Emulated, "{}", log_stream.str());
170 break;
171 case MessageHeader::Severity::Error:
172 LOG_ERROR(Debug_Emulated, "{}", log_stream.str());
173 break;
174 case MessageHeader::Severity::Critical:
175 LOG_CRITICAL(Debug_Emulated, "{}", log_stream.str());
176 break;
177 }
178 }
179 } 62 }
180 63
181 // This service function is intended to be used as a way to
182 // redirect logging output to different destinations, however,
183 // given we always want to see the logging output, it's sufficient
184 // to do nothing and return success here.
185 void SetDestination(Kernel::HLERequestContext& ctx) { 64 void SetDestination(Kernel::HLERequestContext& ctx) {
186 LOG_DEBUG(Service_LM, "called"); 65 IPC::RequestParser rp{ctx};
66 const auto destination = rp.PopEnum<DestinationFlag>();
67
68 LOG_DEBUG(Service_LM, "called, destination={:08X}", static_cast<u32>(destination));
69
70 manager.SetDestination(destination);
187 71
188 IPC::ResponseBuilder rb{ctx, 2}; 72 IPC::ResponseBuilder rb{ctx, 2};
189 rb.Push(RESULT_SUCCESS); 73 rb.Push(RESULT_SUCCESS);
190 } 74 }
191 75
192 std::ostringstream log_stream; 76 Manager& manager;
193}; 77};
194 78
195class LM final : public ServiceFramework<LM> { 79class LM final : public ServiceFramework<LM> {
196public: 80public:
197 explicit LM() : ServiceFramework{"lm"} { 81 explicit LM(Manager& manager) : ServiceFramework{"lm"}, manager(manager) {
82 // clang-format off
198 static const FunctionInfo functions[] = { 83 static const FunctionInfo functions[] = {
199 {0x00000000, &LM::OpenLogger, "OpenLogger"}, 84 {0, &LM::OpenLogger, "OpenLogger"},
200 }; 85 };
86 // clang-format on
87
201 RegisterHandlers(functions); 88 RegisterHandlers(functions);
202 } 89 }
203 90
204 /** 91private:
205 * LM::OpenLogger service function
206 * Inputs:
207 * 0: 0x00000000
208 * Outputs:
209 * 0: ResultCode
210 */
211 void OpenLogger(Kernel::HLERequestContext& ctx) { 92 void OpenLogger(Kernel::HLERequestContext& ctx) {
212 LOG_DEBUG(Service_LM, "called"); 93 LOG_DEBUG(Service_LM, "called");
213 94
214 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 95 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
215 rb.Push(RESULT_SUCCESS); 96 rb.Push(RESULT_SUCCESS);
216 rb.PushIpcInterface<ILogger>(); 97 rb.PushIpcInterface<ILogger>(manager);
217 } 98 }
99
100 Manager& manager;
218}; 101};
219 102
220void InstallInterfaces(SM::ServiceManager& service_manager) { 103void InstallInterfaces(Core::System& system) {
221 std::make_shared<LM>()->InstallAsService(service_manager); 104 std::make_shared<LM>(system.GetLogManager())->InstallAsService(system.ServiceManager());
222} 105}
223 106
224} // namespace Service::LM 107} // namespace Service::LM
diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h
index 7806ae27b..d40410b5c 100644
--- a/src/core/hle/service/lm/lm.h
+++ b/src/core/hle/service/lm/lm.h
@@ -4,13 +4,13 @@
4 4
5#pragma once 5#pragma once
6 6
7namespace Service::SM { 7namespace Core {
8class ServiceManager; 8class System;
9} 9}
10 10
11namespace Service::LM { 11namespace Service::LM {
12 12
13/// Registers all LM services with the specified service manager. 13/// Registers all LM services with the specified service manager.
14void InstallInterfaces(SM::ServiceManager& service_manager); 14void InstallInterfaces(Core::System& system);
15 15
16} // namespace Service::LM 16} // namespace Service::LM
diff --git a/src/core/hle/service/lm/manager.cpp b/src/core/hle/service/lm/manager.cpp
new file mode 100644
index 000000000..b67081b86
--- /dev/null
+++ b/src/core/hle/service/lm/manager.cpp
@@ -0,0 +1,133 @@
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/assert.h"
6#include "common/logging/log.h"
7#include "common/string_util.h"
8#include "core/hle/service/lm/manager.h"
9#include "core/reporter.h"
10
11namespace Service::LM {
12
13std::ostream& operator<<(std::ostream& os, DestinationFlag dest) {
14 std::vector<std::string> array;
15 const auto check_single_flag = [dest, &array](DestinationFlag check, std::string name) {
16 if ((static_cast<u32>(check) & static_cast<u32>(dest)) != 0) {
17 array.emplace_back(std::move(name));
18 }
19 };
20
21 check_single_flag(DestinationFlag::Default, "Default");
22 check_single_flag(DestinationFlag::UART, "UART");
23 check_single_flag(DestinationFlag::UARTSleeping, "UART (Sleeping)");
24
25 os << "[";
26 for (const auto& entry : array) {
27 os << entry << ", ";
28 }
29 return os << "]";
30}
31
32std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity) {
33 switch (severity) {
34 case MessageHeader::Severity::Trace:
35 return os << "Trace";
36 case MessageHeader::Severity::Info:
37 return os << "Info";
38 case MessageHeader::Severity::Warning:
39 return os << "Warning";
40 case MessageHeader::Severity::Error:
41 return os << "Error";
42 case MessageHeader::Severity::Critical:
43 return os << "Critical";
44 default:
45 return os << fmt::format("{:08X}", static_cast<u32>(severity));
46 }
47}
48
49std::ostream& operator<<(std::ostream& os, Field field) {
50 switch (field) {
51 case Field::Skip:
52 return os << "Skip";
53 case Field::Message:
54 return os << "Message";
55 case Field::Line:
56 return os << "Line";
57 case Field::Filename:
58 return os << "Filename";
59 case Field::Function:
60 return os << "Function";
61 case Field::Module:
62 return os << "Module";
63 case Field::Thread:
64 return os << "Thread";
65 default:
66 return os << fmt::format("{:08X}", static_cast<u32>(field));
67 }
68}
69
70std::string FormatField(Field type, const std::vector<u8>& data) {
71 switch (type) {
72 case Field::Skip:
73 return "";
74 case Field::Line:
75 if (data.size() >= sizeof(u32)) {
76 u32 line;
77 std::memcpy(&line, data.data(), sizeof(u32));
78 return fmt::format("{}", line);
79 }
80 return "[ERROR DECODING LINE NUMBER]";
81 case Field::Message:
82 case Field::Filename:
83 case Field::Function:
84 case Field::Module:
85 case Field::Thread:
86 return Common::StringFromFixedZeroTerminatedBuffer(
87 reinterpret_cast<const char*>(data.data()), data.size());
88 default:
89 UNIMPLEMENTED();
90 }
91}
92
93Manager::Manager(Core::Reporter& reporter) : reporter(reporter) {}
94
95Manager::~Manager() = default;
96
97void Manager::SetEnabled(bool enabled) {
98 this->enabled = enabled;
99}
100
101void Manager::SetDestination(DestinationFlag destination) {
102 this->destination = destination;
103}
104
105void Manager::Log(LogMessage message) {
106 if (message.header.IsHeadLog()) {
107 InitializeLog();
108 }
109
110 current_log.emplace_back(std::move(message));
111
112 if (current_log.back().header.IsTailLog()) {
113 FinalizeLog();
114 }
115}
116
117void Manager::Flush() {
118 FinalizeLog();
119}
120
121void Manager::InitializeLog() {
122 current_log.clear();
123
124 LOG_INFO(Service_LM, "Initialized new log session");
125}
126
127void Manager::FinalizeLog() {
128 reporter.SaveLogReport(static_cast<u32>(destination), std::move(current_log));
129
130 LOG_INFO(Service_LM, "Finalized current log session");
131}
132
133} // namespace Service::LM
diff --git a/src/core/hle/service/lm/manager.h b/src/core/hle/service/lm/manager.h
new file mode 100644
index 000000000..544e636ba
--- /dev/null
+++ b/src/core/hle/service/lm/manager.h
@@ -0,0 +1,106 @@
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 <map>
8#include <ostream>
9#include <vector>
10#include "common/bit_field.h"
11#include "common/common_types.h"
12#include "common/swap.h"
13
14namespace Core {
15class Reporter;
16}
17
18namespace Service::LM {
19
20enum class DestinationFlag : u32 {
21 Default = 1,
22 UART = 2,
23 UARTSleeping = 4,
24
25 All = 0xFFFF,
26};
27
28struct MessageHeader {
29 enum Flags : u32_le {
30 IsHead = 1,
31 IsTail = 2,
32 };
33 enum Severity : u32_le {
34 Trace,
35 Info,
36 Warning,
37 Error,
38 Critical,
39 };
40
41 u64_le pid;
42 u64_le thread_context;
43 union {
44 BitField<0, 16, Flags> flags;
45 BitField<16, 8, Severity> severity;
46 BitField<24, 8, u32> verbosity;
47 };
48 u32_le payload_size;
49
50 bool IsHeadLog() const {
51 return flags & IsHead;
52 }
53 bool IsTailLog() const {
54 return flags & IsTail;
55 }
56};
57static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
58
59enum class Field : u8 {
60 Skip = 1,
61 Message = 2,
62 Line = 3,
63 Filename = 4,
64 Function = 5,
65 Module = 6,
66 Thread = 7,
67};
68
69std::ostream& operator<<(std::ostream& os, DestinationFlag dest);
70std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity);
71std::ostream& operator<<(std::ostream& os, Field field);
72
73using FieldMap = std::map<Field, std::vector<u8>>;
74
75struct LogMessage {
76 MessageHeader header;
77 FieldMap fields;
78};
79
80std::string FormatField(Field type, const std::vector<u8>& data);
81
82class Manager {
83public:
84 explicit Manager(Core::Reporter& reporter);
85 ~Manager();
86
87 void SetEnabled(bool enabled);
88 void SetDestination(DestinationFlag destination);
89
90 void Log(LogMessage message);
91
92 void Flush();
93
94private:
95 void InitializeLog();
96 void FinalizeLog();
97
98 bool enabled = true;
99 DestinationFlag destination = DestinationFlag::All;
100
101 std::vector<LogMessage> current_log;
102
103 Core::Reporter& reporter;
104};
105
106} // namespace Service::LM
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 831a427de..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);
@@ -226,7 +226,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
226 LBL::InstallInterfaces(*sm); 226 LBL::InstallInterfaces(*sm);
227 LDN::InstallInterfaces(*sm); 227 LDN::InstallInterfaces(*sm);
228 LDR::InstallInterfaces(*sm, system); 228 LDR::InstallInterfaces(*sm, system);
229 LM::InstallInterfaces(*sm); 229 LM::InstallInterfaces(system);
230 Migration::InstallInterfaces(*sm); 230 Migration::InstallInterfaces(*sm);
231 Mii::InstallInterfaces(*sm); 231 Mii::InstallInterfaces(*sm);
232 MM::InstallInterfaces(*sm); 232 MM::InstallInterfaces(*sm);
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/reporter.cpp b/src/core/reporter.cpp
index 9c657929e..6f4af77fd 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -7,6 +7,7 @@
7 7
8#include <fmt/chrono.h> 8#include <fmt/chrono.h>
9#include <fmt/format.h> 9#include <fmt/format.h>
10#include <fmt/ostream.h>
10#include <json.hpp> 11#include <json.hpp>
11 12
12#include "common/file_util.h" 13#include "common/file_util.h"
@@ -17,6 +18,7 @@
17#include "core/hle/kernel/hle_ipc.h" 18#include "core/hle/kernel/hle_ipc.h"
18#include "core/hle/kernel/process.h" 19#include "core/hle/kernel/process.h"
19#include "core/hle/result.h" 20#include "core/hle/result.h"
21#include "core/hle/service/lm/manager.h"
20#include "core/reporter.h" 22#include "core/reporter.h"
21#include "core/settings.h" 23#include "core/settings.h"
22 24
@@ -354,6 +356,55 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
354 SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); 356 SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
355} 357}
356 358
359void Reporter::SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const {
360 if (!IsReportingEnabled()) {
361 return;
362 }
363
364 const auto timestamp = GetTimestamp();
365 json out;
366
367 out["yuzu_version"] = GetYuzuVersionData();
368 out["report_common"] =
369 GetReportCommonData(system.CurrentProcess()->GetTitleID(), RESULT_SUCCESS, timestamp);
370
371 out["log_destination"] =
372 fmt::format("{}", static_cast<Service::LM::DestinationFlag>(destination));
373
374 auto json_messages = json::array();
375 std::transform(messages.begin(), messages.end(), std::back_inserter(json_messages),
376 [](const Service::LM::LogMessage& message) {
377 json out;
378 out["is_head"] = fmt::format("{}", message.header.IsHeadLog());
379 out["is_tail"] = fmt::format("{}", message.header.IsTailLog());
380 out["pid"] = fmt::format("{:016X}", message.header.pid);
381 out["thread_context"] =
382 fmt::format("{:016X}", message.header.thread_context);
383 out["payload_size"] = fmt::format("{:016X}", message.header.payload_size);
384 out["flags"] = fmt::format("{:04X}", message.header.flags.Value());
385 out["severity"] = fmt::format("{}", message.header.severity.Value());
386 out["verbosity"] = fmt::format("{:02X}", message.header.verbosity);
387
388 auto fields = json::array();
389 std::transform(message.fields.begin(), message.fields.end(),
390 std::back_inserter(fields), [](const auto& kv) {
391 json out;
392 out["type"] = fmt::format("{}", kv.first);
393 out["data"] =
394 Service::LM::FormatField(kv.first, kv.second);
395 return out;
396 });
397
398 out["fields"] = std::move(fields);
399 return out;
400 });
401
402 out["log_messages"] = std::move(json_messages);
403
404 SaveToFile(std::move(out),
405 GetPath("log_report", system.CurrentProcess()->GetTitleID(), timestamp));
406}
407
357void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, 408void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
358 std::string log_message) const { 409 std::string log_message) const {
359 if (!IsReportingEnabled()) 410 if (!IsReportingEnabled())
diff --git a/src/core/reporter.h b/src/core/reporter.h
index f08aa11fb..380941b1b 100644
--- a/src/core/reporter.h
+++ b/src/core/reporter.h
@@ -20,6 +20,10 @@ namespace Service::FileSystem {
20enum class LogMode : u32; 20enum class LogMode : u32;
21} 21}
22 22
23namespace Service::LM {
24struct LogMessage;
25} // namespace Service::LM
26
23namespace Core { 27namespace Core {
24 28
25class System; 29class System;
@@ -29,18 +33,22 @@ public:
29 explicit Reporter(System& system); 33 explicit Reporter(System& system);
30 ~Reporter(); 34 ~Reporter();
31 35
36 // Used by fatal services
32 void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp, 37 void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
33 u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, 38 u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
34 const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace, 39 const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
35 u32 backtrace_size, const std::string& arch, u32 unk10) const; 40 u32 backtrace_size, const std::string& arch, u32 unk10) const;
36 41
42 // Used by syscall svcBreak
37 void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, 43 void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
38 std::optional<std::vector<u8>> resolved_buffer = {}) const; 44 std::optional<std::vector<u8>> resolved_buffer = {}) const;
39 45
46 // Used by HLE service handler
40 void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, 47 void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
41 const std::string& name, 48 const std::string& name,
42 const std::string& service_name) const; 49 const std::string& service_name) const;
43 50
51 // Used by stub applet implementation
44 void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version, 52 void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version,
45 u32 theme_color, bool startup_sound, u64 system_tick, 53 u32 theme_color, bool startup_sound, u64 system_tick,
46 std::vector<std::vector<u8>> normal_channel, 54 std::vector<std::vector<u8>> normal_channel,
@@ -55,6 +63,7 @@ public:
55 void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data, 63 void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data,
56 std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const; 64 std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const;
57 65
66 // Used by error applet
58 void SaveErrorReport(u64 title_id, ResultCode result, 67 void SaveErrorReport(u64 title_id, ResultCode result,
59 std::optional<std::string> custom_text_main = {}, 68 std::optional<std::string> custom_text_main = {},
60 std::optional<std::string> custom_text_detail = {}) const; 69 std::optional<std::string> custom_text_detail = {}) const;
@@ -62,6 +71,11 @@ public:
62 void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, 71 void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
63 std::string log_message) const; 72 std::string log_message) const;
64 73
74 // Used by lm services
75 void SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const;
76
77 // Can be used anywhere to generate a backtrace and general info report at any point during
78 // execution. Not intended to be used for anything other than debugging or testing.
65 void SaveUserReport() const; 79 void SaveUserReport() const;
66 80
67private: 81private:
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/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index b318aedb8..7802fd808 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -249,6 +249,11 @@ void Maxwell3D::InitDirtySettings() {
249 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_units)] = polygon_offset_dirty_reg; 249 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_units)] = polygon_offset_dirty_reg;
250 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_factor)] = polygon_offset_dirty_reg; 250 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_factor)] = polygon_offset_dirty_reg;
251 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; 251 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg;
252
253 // Depth bounds
254 constexpr u32 depth_bounds_values_dirty_reg = DIRTY_REGS_POS(depth_bounds_values);
255 dirty_pointers[MAXWELL3D_REG_INDEX(depth_bounds[0])] = depth_bounds_values_dirty_reg;
256 dirty_pointers[MAXWELL3D_REG_INDEX(depth_bounds[1])] = depth_bounds_values_dirty_reg;
252} 257}
253 258
254void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) { 259void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) {
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 4c97759ed..e3f1047d5 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -687,7 +687,9 @@ public:
687 687
688 u32 rt_separate_frag_data; 688 u32 rt_separate_frag_data;
689 689
690 INSERT_PADDING_WORDS(0xC); 690 f32 depth_bounds[2];
691
692 INSERT_PADDING_WORDS(0xA);
691 693
692 struct { 694 struct {
693 u32 address_high; 695 u32 address_high;
@@ -1201,6 +1203,7 @@ public:
1201 bool transform_feedback; 1203 bool transform_feedback;
1202 bool color_mask; 1204 bool color_mask;
1203 bool polygon_offset; 1205 bool polygon_offset;
1206 bool depth_bounds_values;
1204 1207
1205 // Complementary 1208 // Complementary
1206 bool viewport_transform; 1209 bool viewport_transform;
@@ -1400,6 +1403,7 @@ ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
1400ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); 1403ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
1401ASSERT_REG_POSITION(color_mask_common, 0x3E4); 1404ASSERT_REG_POSITION(color_mask_common, 0x3E4);
1402ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB); 1405ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
1406ASSERT_REG_POSITION(depth_bounds, 0x3EC);
1403ASSERT_REG_POSITION(zeta, 0x3F8); 1407ASSERT_REG_POSITION(zeta, 0x3F8);
1404ASSERT_REG_POSITION(clear_flags, 0x43E); 1408ASSERT_REG_POSITION(clear_flags, 0x43E);
1405ASSERT_REG_POSITION(vertex_attrib_format, 0x458); 1409ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
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_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 6a17bed72..a85f730a8 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1340,7 +1340,9 @@ void RasterizerOpenGL::SyncPolygonOffset() {
1340 state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0; 1340 state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0;
1341 state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0; 1341 state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0;
1342 state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0; 1342 state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0;
1343 state.polygon_offset.units = regs.polygon_offset_units; 1343
1344 // Hardware divides polygon offset units by two
1345 state.polygon_offset.units = regs.polygon_offset_units / 2.0f;
1344 state.polygon_offset.factor = regs.polygon_offset_factor; 1346 state.polygon_offset.factor = regs.polygon_offset_factor;
1345 state.polygon_offset.clamp = regs.polygon_offset_clamp; 1347 state.polygon_offset.clamp = regs.polygon_offset_clamp;
1346 1348
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..f92a4b3c3 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
@@ -705,6 +716,8 @@ void Config::ReadUIValues() {
705 UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt(); 716 UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
706 UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool(); 717 UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
707 UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt(); 718 UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt();
719 UISettings::values.pause_when_in_background =
720 ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
708 721
709 ApplyDefaultProfileIfInputInvalid(); 722 ApplyDefaultProfileIfInputInvalid();
710 723
@@ -769,6 +782,7 @@ void Config::ReadValues() {
769 ReadMiscellaneousValues(); 782 ReadMiscellaneousValues();
770 ReadDebuggingValues(); 783 ReadDebuggingValues();
771 ReadWebServiceValues(); 784 ReadWebServiceValues();
785 ReadServiceValues();
772 ReadDisabledAddOnValues(); 786 ReadDisabledAddOnValues();
773 ReadUIValues(); 787 ReadUIValues();
774} 788}
@@ -866,6 +880,7 @@ void Config::SaveValues() {
866 SaveMiscellaneousValues(); 880 SaveMiscellaneousValues();
867 SaveDebuggingValues(); 881 SaveDebuggingValues();
868 SaveWebServiceValues(); 882 SaveWebServiceValues();
883 SaveServiceValues();
869 SaveDisabledAddOnValues(); 884 SaveDisabledAddOnValues();
870 SaveUIValues(); 885 SaveUIValues();
871} 886}
@@ -963,6 +978,14 @@ void Config::SaveDebuggingValues() {
963 qt_config->endGroup(); 978 qt_config->endGroup();
964} 979}
965 980
981void Config::SaveServiceValues() {
982 qt_config->beginGroup(QStringLiteral("Services"));
983 WriteSetting(QStringLiteral("bcat_backend"),
984 QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null"));
985 WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false);
986 qt_config->endGroup();
987}
988
966void Config::SaveDisabledAddOnValues() { 989void Config::SaveDisabledAddOnValues() {
967 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns")); 990 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
968 991
@@ -1103,6 +1126,8 @@ void Config::SaveUIValues() {
1103 WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0); 1126 WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
1104 WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false); 1127 WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
1105 WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0); 1128 WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0);
1129 WriteSetting(QStringLiteral("pauseWhenInBackground"),
1130 UISettings::values.pause_when_in_background, false);
1106 1131
1107 qt_config->endGroup(); 1132 qt_config->endGroup();
1108} 1133}
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_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 98bc9b391..34e1d7fea 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -31,6 +31,7 @@ void ConfigureGeneral::SetConfiguration() {
31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
33 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 33 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
34 ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
34 35
35 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); 36 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
36 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); 37 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
@@ -42,6 +43,7 @@ void ConfigureGeneral::ApplyConfiguration() {
42 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 43 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
43 UISettings::values.theme = 44 UISettings::values.theme =
44 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); 45 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
46 UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
45 47
46 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); 48 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
47 Settings::values.frame_limit = ui->frame_limit->value(); 49 Settings::values.frame_limit = ui->frame_limit->value();
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 0bb91d64b..26b3486ff 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -65,6 +65,13 @@
65 </property> 65 </property>
66 </widget> 66 </widget>
67 </item> 67 </item>
68 <item>
69 <widget class="QCheckBox" name="toggle_background_pause">
70 <property name="text">
71 <string>Pause emulation when in background</string>
72 </property>
73 </widget>
74 </item>
68 </layout> 75 </layout>
69 </item> 76 </item>
70 </layout> 77 </layout>
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..d6bb18d24 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -675,6 +675,24 @@ void GMainWindow::RestoreUIState() {
675 Debugger::ToggleConsole(); 675 Debugger::ToggleConsole();
676} 676}
677 677
678void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
679 if (!UISettings::values.pause_when_in_background) {
680 return;
681 }
682 if (state != Qt::ApplicationHidden && state != Qt::ApplicationInactive &&
683 state != Qt::ApplicationActive) {
684 LOG_DEBUG(Frontend, "ApplicationState unusual flag: {} ", state);
685 }
686 if (ui.action_Pause->isEnabled() &&
687 (state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
688 auto_paused = true;
689 OnPauseGame();
690 } else if (ui.action_Start->isEnabled() && auto_paused && state == Qt::ApplicationActive) {
691 auto_paused = false;
692 OnStartGame();
693 }
694}
695
678void GMainWindow::ConnectWidgetEvents() { 696void GMainWindow::ConnectWidgetEvents() {
679 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 697 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
680 connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); 698 connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
@@ -1889,15 +1907,24 @@ void GMainWindow::OnCaptureScreenshot() {
1889} 1907}
1890 1908
1891void GMainWindow::UpdateWindowTitle(const QString& title_name) { 1909void GMainWindow::UpdateWindowTitle(const QString& title_name) {
1892 const QString full_name = QString::fromUtf8(Common::g_build_fullname); 1910 const auto full_name = std::string(Common::g_build_fullname);
1893 const QString branch_name = QString::fromUtf8(Common::g_scm_branch); 1911 const auto branch_name = std::string(Common::g_scm_branch);
1894 const QString description = QString::fromUtf8(Common::g_scm_desc); 1912 const auto description = std::string(Common::g_scm_desc);
1913 const auto build_id = std::string(Common::g_build_id);
1914
1915 const auto date =
1916 QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();
1895 1917
1896 if (title_name.isEmpty()) { 1918 if (title_name.isEmpty()) {
1897 setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description)); 1919 const auto fmt = std::string(Common::g_title_bar_format_idle);
1920 setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt,
1921 full_name, branch_name, description,
1922 std::string{}, date, build_id)));
1898 } else { 1923 } else {
1899 setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3") 1924 const auto fmt = std::string(Common::g_title_bar_format_running);
1900 .arg(full_name, branch_name, description, title_name)); 1925 setWindowTitle(QString::fromStdString(
1926 fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name,
1927 description, title_name.toStdString(), date, build_id)));
1901 } 1928 }
1902} 1929}
1903 1930
@@ -2311,6 +2338,9 @@ int main(int argc, char* argv[]) {
2311 // After settings have been loaded by GMainWindow, apply the filter 2338 // After settings have been loaded by GMainWindow, apply the filter
2312 main_window.show(); 2339 main_window.show();
2313 2340
2341 QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
2342 &GMainWindow::OnAppFocusStateChanged);
2343
2314 Settings::LogSettings(); 2344 Settings::LogSettings();
2315 2345
2316 int result = app.exec(); 2346 int result = app.exec();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e942d1248..fd4b9ccf5 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -119,6 +119,7 @@ public slots:
119 void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); 119 void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
120 void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); 120 void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
121 void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); 121 void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
122 void OnAppFocusStateChanged(Qt::ApplicationState state);
122 123
123private: 124private:
124 void InitializeWidgets(); 125 void InitializeWidgets();
@@ -244,6 +245,8 @@ private:
244 // The path to the game currently running 245 // The path to the game currently running
245 QString game_path; 246 QString game_path;
246 247
248 bool auto_paused = false;
249
247 // FS 250 // FS
248 std::shared_ptr<FileSys::VfsFilesystem> vfs; 251 std::shared_ptr<FileSys::VfsFilesystem> vfs;
249 std::unique_ptr<FileSys::ManualContentProvider> provider; 252 std::unique_ptr<FileSys::ManualContentProvider> provider;
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..bc7725a01 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 {
@@ -58,6 +58,7 @@ struct Values {
58 58
59 bool confirm_before_closing; 59 bool confirm_before_closing;
60 bool first_start; 60 bool first_start;
61 bool pause_when_in_background;
61 62
62 bool select_user_on_boot; 63 bool select_user_on_boot;
63 64
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);