summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/audio_renderer.cpp10
-rw-r--r--src/audio_core/audio_renderer.h25
-rw-r--r--src/common/CMakeLists.txt3
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/core.cpp20
-rw-r--r--src/core/core.h8
-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/vfs_libzip.cpp79
-rw-r--r--src/core/file_sys/vfs_libzip.h13
-rw-r--r--src/core/hle/service/acc/acc.cpp14
-rw-r--r--src/core/hle/service/am/am.cpp98
-rw-r--r--src/core/hle/service/am/am.h7
-rw-r--r--src/core/hle/service/am/applet_ae.h2
-rw-r--r--src/core/hle/service/am/applet_oe.h2
-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/bcat/backend/backend.cpp136
-rw-r--r--src/core/hle/service/bcat/backend/backend.h147
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp503
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h58
-rw-r--r--src/core/hle/service/bcat/bcat.cpp8
-rw-r--r--src/core/hle/service/bcat/bcat.h3
-rw-r--r--src/core/hle/service/bcat/module.cpp557
-rw-r--r--src/core/hle/service/bcat/module.h24
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp9
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp24
-rw-r--r--src/core/hle/service/hid/controllers/npad.h5
-rw-r--r--src/core/hle/service/hid/hid.cpp2
-rw-r--r--src/core/hle/service/nifm/nifm.cpp13
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp18
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp20
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h5
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp75
-rw-r--r--src/core/hle/service/nvdrv/interface.h3
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h6
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp7
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h5
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/settings.cpp2
-rw-r--r--src/core/settings.h4
-rw-r--r--src/video_core/engines/shader_bytecode.h8
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp174
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp15
-rw-r--r--src/video_core/shader/decode/image.cpp137
-rw-r--r--src/video_core/shader/node.h46
-rw-r--r--src/video_core/shader/shader_ir.h9
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp21
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_general.cpp1
-rw-r--r--src/yuzu/configuration/configure_service.cpp136
-rw-r--r--src/yuzu/configuration/configure_service.h34
-rw-r--r--src/yuzu/configuration/configure_service.ui124
-rw-r--r--src/yuzu/main.cpp43
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h5
83 files changed, 2493 insertions, 398 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index da50a0bbc..e6f38d600 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -107,6 +107,11 @@ Stream::State AudioRenderer::GetStreamState() const {
107 return stream->GetState(); 107 return stream->GetState();
108} 108}
109 109
110static constexpr u32 VersionFromRevision(u32_le rev) {
111 // "REV7" -> 7
112 return ((rev >> 24) & 0xff) - 0x30;
113}
114
110std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { 115std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
111 // Copy UpdateDataHeader struct 116 // Copy UpdateDataHeader struct
112 UpdateDataHeader config{}; 117 UpdateDataHeader config{};
@@ -166,6 +171,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
166 // Copy output header 171 // Copy output header
167 UpdateDataHeader response_data{worker_params}; 172 UpdateDataHeader response_data{worker_params};
168 std::vector<u8> output_params(response_data.total_size); 173 std::vector<u8> output_params(response_data.total_size);
174 const auto audren_revision = VersionFromRevision(config.revision);
175 if (audren_revision >= 5) {
176 response_data.frame_count = 0x10;
177 response_data.total_size += 0x10;
178 }
169 std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); 179 std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
170 180
171 // Copy output memory pool entries 181 // Copy output memory pool entries
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index 45afbe759..4f14b91cd 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -194,21 +194,24 @@ struct UpdateDataHeader {
194 mixes_size = 0x0; 194 mixes_size = 0x0;
195 sinks_size = config.sink_count * 0x20; 195 sinks_size = config.sink_count * 0x20;
196 performance_manager_size = 0x10; 196 performance_manager_size = 0x10;
197 frame_count = 0;
197 total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + 198 total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
198 effects_size + sinks_size + performance_manager_size; 199 effects_size + sinks_size + performance_manager_size;
199 } 200 }
200 201
201 u32_le revision; 202 u32_le revision{};
202 u32_le behavior_size; 203 u32_le behavior_size{};
203 u32_le memory_pools_size; 204 u32_le memory_pools_size{};
204 u32_le voices_size; 205 u32_le voices_size{};
205 u32_le voice_resource_size; 206 u32_le voice_resource_size{};
206 u32_le effects_size; 207 u32_le effects_size{};
207 u32_le mixes_size; 208 u32_le mixes_size{};
208 u32_le sinks_size; 209 u32_le sinks_size{};
209 u32_le performance_manager_size; 210 u32_le performance_manager_size{};
210 INSERT_PADDING_WORDS(6); 211 INSERT_PADDING_WORDS(1);
211 u32_le total_size; 212 u32_le frame_count{};
213 INSERT_PADDING_WORDS(4);
214 u32_le total_size{};
212}; 215};
213static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); 216static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
214 217
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 01abdb3bb..dfed8b51d 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -10,6 +10,9 @@ if (DEFINED ENV{CI})
10 elseif(DEFINED ENV{APPVEYOR}) 10 elseif(DEFINED ENV{APPVEYOR})
11 set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) 11 set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
12 set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) 12 set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
13 elseif(DEFINED ENV{AZURE})
14 set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME})
15 set(BUILD_TAG $ENV{AZURE_REPO_TAG})
13 endif() 16 endif()
14endif() 17endif()
15add_custom_command(OUTPUT scm_rev.cpp 18add_custom_command(OUTPUT scm_rev.cpp
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a6b56c9c6..3416854db 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
@@ -499,6 +510,15 @@ create_target_directory_groups(core)
499 510
500target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 511target_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) 512target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
513
514if (YUZU_ENABLE_BOXCAT)
515 get_directory_property(OPENSSL_LIBS
516 DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
517 DEFINITION OPENSSL_LIBS)
518 target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
519 target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
520endif()
521
502if (ENABLE_WEB_SERVICE) 522if (ENABLE_WEB_SERVICE)
503 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) 523 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
504 target_link_libraries(core PRIVATE web_service) 524 target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 76bb2bae9..75a7ffb97 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -163,6 +163,7 @@ struct System::Impl {
163 gpu_core = VideoCore::CreateGPU(system); 163 gpu_core = VideoCore::CreateGPU(system);
164 164
165 is_powered_on = true; 165 is_powered_on = true;
166 exit_lock = false;
166 167
167 LOG_DEBUG(Core, "Initialized OK"); 168 LOG_DEBUG(Core, "Initialized OK");
168 169
@@ -249,6 +250,7 @@ struct System::Impl {
249 perf_stats->GetMeanFrametime()); 250 perf_stats->GetMeanFrametime());
250 251
251 is_powered_on = false; 252 is_powered_on = false;
253 exit_lock = false;
252 254
253 // Shutdown emulation session 255 // Shutdown emulation session
254 renderer.reset(); 256 renderer.reset();
@@ -333,9 +335,11 @@ struct System::Impl {
333 std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager; 335 std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager;
334 CpuCoreManager cpu_core_manager; 336 CpuCoreManager cpu_core_manager;
335 bool is_powered_on = false; 337 bool is_powered_on = false;
338 bool exit_lock = false;
336 339
337 std::unique_ptr<Memory::CheatEngine> cheat_engine; 340 std::unique_ptr<Memory::CheatEngine> cheat_engine;
338 std::unique_ptr<Tools::Freezer> memory_freezer; 341 std::unique_ptr<Tools::Freezer> memory_freezer;
342 std::array<u8, 0x20> build_id{};
339 343
340 /// Frontend applets 344 /// Frontend applets
341 Service::AM::Applets::AppletManager applet_manager; 345 Service::AM::Applets::AppletManager applet_manager;
@@ -629,6 +633,22 @@ const Service::APM::Controller& System::GetAPMController() const {
629 return impl->apm_controller; 633 return impl->apm_controller;
630} 634}
631 635
636void System::SetExitLock(bool locked) {
637 impl->exit_lock = locked;
638}
639
640bool System::GetExitLock() const {
641 return impl->exit_lock;
642}
643
644void System::SetCurrentProcessBuildID(std::array<u8, 32> id) {
645 impl->build_id = id;
646}
647
648const std::array<u8, 32>& System::GetCurrentProcessBuildID() const {
649 return impl->build_id;
650}
651
632System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { 652System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
633 return impl->Init(*this, emu_window); 653 return impl->Init(*this, emu_window);
634} 654}
diff --git a/src/core/core.h b/src/core/core.h
index d2a3c82d8..f49b7fbf9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -326,6 +326,14 @@ public:
326 326
327 const Service::APM::Controller& GetAPMController() const; 327 const Service::APM::Controller& GetAPMController() const;
328 328
329 void SetExitLock(bool locked);
330
331 bool GetExitLock() const;
332
333 void SetCurrentProcessBuildID(std::array<u8, 0x20> id);
334
335 const std::array<u8, 0x20>& GetCurrentProcessBuildID() const;
336
329private: 337private:
330 System(); 338 System();
331 339
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/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/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index a7c55e116..0c0f7ed6e 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -70,7 +70,7 @@ public:
70 70
71protected: 71protected:
72 void Get(Kernel::HLERequestContext& ctx) { 72 void Get(Kernel::HLERequestContext& ctx) {
73 LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); 73 LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
74 ProfileBase profile_base{}; 74 ProfileBase profile_base{};
75 ProfileData data{}; 75 ProfileData data{};
76 if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { 76 if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) {
@@ -89,7 +89,7 @@ protected:
89 } 89 }
90 90
91 void GetBase(Kernel::HLERequestContext& ctx) { 91 void GetBase(Kernel::HLERequestContext& ctx) {
92 LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); 92 LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
93 ProfileBase profile_base{}; 93 ProfileBase profile_base{};
94 if (profile_manager.GetProfileBase(user_id, profile_base)) { 94 if (profile_manager.GetProfileBase(user_id, profile_base)) {
95 IPC::ResponseBuilder rb{ctx, 16}; 95 IPC::ResponseBuilder rb{ctx, 16};
@@ -263,7 +263,7 @@ private:
263}; 263};
264 264
265void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { 265void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
266 LOG_INFO(Service_ACC, "called"); 266 LOG_DEBUG(Service_ACC, "called");
267 IPC::ResponseBuilder rb{ctx, 3}; 267 IPC::ResponseBuilder rb{ctx, 3};
268 rb.Push(RESULT_SUCCESS); 268 rb.Push(RESULT_SUCCESS);
269 rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); 269 rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount()));
@@ -272,7 +272,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) {
272void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { 272void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
273 IPC::RequestParser rp{ctx}; 273 IPC::RequestParser rp{ctx};
274 Common::UUID user_id = rp.PopRaw<Common::UUID>(); 274 Common::UUID user_id = rp.PopRaw<Common::UUID>();
275 LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); 275 LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format());
276 276
277 IPC::ResponseBuilder rb{ctx, 3}; 277 IPC::ResponseBuilder rb{ctx, 3};
278 rb.Push(RESULT_SUCCESS); 278 rb.Push(RESULT_SUCCESS);
@@ -280,21 +280,21 @@ void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) {
280} 280}
281 281
282void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { 282void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) {
283 LOG_INFO(Service_ACC, "called"); 283 LOG_DEBUG(Service_ACC, "called");
284 ctx.WriteBuffer(profile_manager->GetAllUsers()); 284 ctx.WriteBuffer(profile_manager->GetAllUsers());
285 IPC::ResponseBuilder rb{ctx, 2}; 285 IPC::ResponseBuilder rb{ctx, 2};
286 rb.Push(RESULT_SUCCESS); 286 rb.Push(RESULT_SUCCESS);
287} 287}
288 288
289void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { 289void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) {
290 LOG_INFO(Service_ACC, "called"); 290 LOG_DEBUG(Service_ACC, "called");
291 ctx.WriteBuffer(profile_manager->GetOpenUsers()); 291 ctx.WriteBuffer(profile_manager->GetOpenUsers());
292 IPC::ResponseBuilder rb{ctx, 2}; 292 IPC::ResponseBuilder rb{ctx, 2};
293 rb.Push(RESULT_SUCCESS); 293 rb.Push(RESULT_SUCCESS);
294} 294}
295 295
296void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { 296void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) {
297 LOG_INFO(Service_ACC, "called"); 297 LOG_DEBUG(Service_ACC, "called");
298 IPC::ResponseBuilder rb{ctx, 6}; 298 IPC::ResponseBuilder rb{ctx, 6};
299 rb.Push(RESULT_SUCCESS); 299 rb.Push(RESULT_SUCCESS);
300 rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser()); 300 rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser());
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 3366fd8ce..34409e0c3 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_} {
@@ -232,12 +238,12 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
232 238
233IDebugFunctions::~IDebugFunctions() = default; 239IDebugFunctions::~IDebugFunctions() = default;
234 240
235ISelfController::ISelfController(Core::System& system_, 241ISelfController::ISelfController(Core::System& system,
236 std::shared_ptr<NVFlinger::NVFlinger> nvflinger_) 242 std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
237 : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) { 243 : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) {
238 // clang-format off 244 // clang-format off
239 static const FunctionInfo functions[] = { 245 static const FunctionInfo functions[] = {
240 {0, nullptr, "Exit"}, 246 {0, &ISelfController::Exit, "Exit"},
241 {1, &ISelfController::LockExit, "LockExit"}, 247 {1, &ISelfController::LockExit, "LockExit"},
242 {2, &ISelfController::UnlockExit, "UnlockExit"}, 248 {2, &ISelfController::UnlockExit, "UnlockExit"},
243 {3, &ISelfController::EnterFatalSection, "EnterFatalSection"}, 249 {3, &ISelfController::EnterFatalSection, "EnterFatalSection"},
@@ -282,7 +288,7 @@ ISelfController::ISelfController(Core::System& system_,
282 288
283 RegisterHandlers(functions); 289 RegisterHandlers(functions);
284 290
285 auto& kernel = system_.Kernel(); 291 auto& kernel = system.Kernel();
286 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, 292 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
287 "ISelfController:LaunchableEvent"); 293 "ISelfController:LaunchableEvent");
288 294
@@ -298,15 +304,28 @@ ISelfController::ISelfController(Core::System& system_,
298 304
299ISelfController::~ISelfController() = default; 305ISelfController::~ISelfController() = default;
300 306
307void ISelfController::Exit(Kernel::HLERequestContext& ctx) {
308 LOG_DEBUG(Service_AM, "called");
309
310 system.Shutdown();
311
312 IPC::ResponseBuilder rb{ctx, 2};
313 rb.Push(RESULT_SUCCESS);
314}
315
301void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { 316void ISelfController::LockExit(Kernel::HLERequestContext& ctx) {
302 LOG_WARNING(Service_AM, "(STUBBED) called"); 317 LOG_DEBUG(Service_AM, "called");
318
319 system.SetExitLock(true);
303 320
304 IPC::ResponseBuilder rb{ctx, 2}; 321 IPC::ResponseBuilder rb{ctx, 2};
305 rb.Push(RESULT_SUCCESS); 322 rb.Push(RESULT_SUCCESS);
306} 323}
307 324
308void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { 325void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) {
309 LOG_WARNING(Service_AM, "(STUBBED) called"); 326 LOG_DEBUG(Service_AM, "called");
327
328 system.SetExitLock(false);
310 329
311 IPC::ResponseBuilder rb{ctx, 2}; 330 IPC::ResponseBuilder rb{ctx, 2};
312 rb.Push(RESULT_SUCCESS); 331 rb.Push(RESULT_SUCCESS);
@@ -550,6 +569,10 @@ void AppletMessageQueue::OperationModeChanged() {
550 on_operation_mode_changed.writable->Signal(); 569 on_operation_mode_changed.writable->Signal();
551} 570}
552 571
572void AppletMessageQueue::RequestExit() {
573 PushMessage(AppletMessage::ExitRequested);
574}
575
553ICommonStateGetter::ICommonStateGetter(Core::System& system, 576ICommonStateGetter::ICommonStateGetter(Core::System& system,
554 std::shared_ptr<AppletMessageQueue> msg_queue) 577 std::shared_ptr<AppletMessageQueue> msg_queue)
555 : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) { 578 : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) {
@@ -1111,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
1111} 1134}
1112 1135
1113void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { 1136void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
1114 LOG_DEBUG(Service_AM, "called"); 1137 IPC::RequestParser rp{ctx};
1138 const auto kind = rp.PopEnum<LaunchParameterKind>();
1115 1139
1116 LaunchParameters params{}; 1140 LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
1117 1141
1118 params.magic = POP_LAUNCH_PARAMETER_MAGIC; 1142 if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
1119 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 = Core::System::GetInstance().GetCurrentProcessBuildID();
1146 u64 build_id{};
1147 std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
1120 1148
1121 Account::ProfileManager profile_manager{}; 1149 const auto data =
1122 const auto uuid = profile_manager.GetUser(Settings::values.current_user); 1150 backend->GetLaunchParameter({Core::CurrentProcess()->GetTitleID(), build_id});
1123 ASSERT(uuid);
1124 params.current_user = uuid->uuid;
1125 1151
1126 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{};
1127 1162
1128 rb.Push(RESULT_SUCCESS); 1163 params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
1164 params.is_account_selected = 1;
1165
1166 Account::ProfileManager profile_manager{};
1167 const auto uuid = profile_manager.GetUser(Settings::values.current_user);
1168 ASSERT(uuid);
1169 params.current_user = uuid->uuid;
1129 1170
1130 std::vector<u8> buffer(sizeof(LaunchParameters)); 1171 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1131 std::memcpy(buffer.data(), &params, buffer.size()); 1172
1173 rb.Push(RESULT_SUCCESS);
1174
1175 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
1176 std::memcpy(buffer.data(), &params, buffer.size());
1132 1177
1133 rb.PushIpcInterface<AM::IStorage>(buffer); 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);
1134} 1186}
1135 1187
1136void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( 1188void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 28f870302..9169eb2bd 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -45,6 +45,7 @@ class AppletMessageQueue {
45public: 45public:
46 enum class AppletMessage : u32 { 46 enum class AppletMessage : u32 {
47 NoMessage = 0, 47 NoMessage = 0,
48 ExitRequested = 4,
48 FocusStateChanged = 15, 49 FocusStateChanged = 15,
49 OperationModeChanged = 30, 50 OperationModeChanged = 30,
50 PerformanceModeChanged = 31, 51 PerformanceModeChanged = 31,
@@ -59,6 +60,7 @@ public:
59 AppletMessage PopMessage(); 60 AppletMessage PopMessage();
60 std::size_t GetMessageCount() const; 61 std::size_t GetMessageCount() const;
61 void OperationModeChanged(); 62 void OperationModeChanged();
63 void RequestExit();
62 64
63private: 65private:
64 std::queue<AppletMessage> messages; 66 std::queue<AppletMessage> messages;
@@ -123,6 +125,7 @@ public:
123 ~ISelfController() override; 125 ~ISelfController() override;
124 126
125private: 127private:
128 void Exit(Kernel::HLERequestContext& ctx);
126 void LockExit(Kernel::HLERequestContext& ctx); 129 void LockExit(Kernel::HLERequestContext& ctx);
127 void UnlockExit(Kernel::HLERequestContext& ctx); 130 void UnlockExit(Kernel::HLERequestContext& ctx);
128 void EnterFatalSection(Kernel::HLERequestContext& ctx); 131 void EnterFatalSection(Kernel::HLERequestContext& ctx);
@@ -151,6 +154,8 @@ private:
151 u32 idle_time_detection_extension = 0; 154 u32 idle_time_detection_extension = 0;
152 u64 num_fatal_sections_entered = 0; 155 u64 num_fatal_sections_entered = 0;
153 bool is_auto_sleep_disabled = false; 156 bool is_auto_sleep_disabled = false;
157
158 Core::System& system;
154}; 159};
155 160
156class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 161class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
@@ -250,6 +255,8 @@ private:
250 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); 255 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
251 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); 256 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
252 257
258 bool launch_popped_application_specific = false;
259 bool launch_popped_account_preselect = false;
253 Kernel::EventPair gpu_error_detected_event; 260 Kernel::EventPair gpu_error_detected_event;
254 Core::System& system; 261 Core::System& system;
255}; 262};
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 0e0d10858..2e3e45915 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -19,6 +19,8 @@ class NVFlinger;
19 19
20namespace AM { 20namespace AM {
21 21
22class AppletMessageQueue;
23
22class AppletAE final : public ServiceFramework<AppletAE> { 24class AppletAE final : public ServiceFramework<AppletAE> {
23public: 25public:
24 explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, 26 explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index 99a65e7b5..758da792d 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -19,6 +19,8 @@ class NVFlinger;
19 19
20namespace AM { 20namespace AM {
21 21
22class AppletMessageQueue;
23
22class AppletOE final : public ServiceFramework<AppletOE> { 24class AppletOE final : public ServiceFramework<AppletOE> {
23public: 25public:
24 explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, 26 explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
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/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
new file mode 100644
index 000000000..9b677debe
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -0,0 +1,136 @@
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 event_name) : impl{} {
14 auto& kernel{Core::System::GetInstance().Kernel()};
15 event = Kernel::WritableEvent::CreateEventPair(
16 kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name);
17}
18
19Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
20 return event.readable;
21}
22
23DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
24 return impl;
25}
26
27void ProgressServiceBackend::SetNeedHLELock(bool need) {
28 need_hle_lock = need;
29}
30
31void ProgressServiceBackend::SetTotalSize(u64 size) {
32 impl.total_bytes = size;
33 SignalUpdate();
34}
35
36void ProgressServiceBackend::StartConnecting() {
37 impl.status = DeliveryCacheProgressImpl::Status::Connecting;
38 SignalUpdate();
39}
40
41void ProgressServiceBackend::StartProcessingDataList() {
42 impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
43 SignalUpdate();
44}
45
46void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
47 std::string_view file_name, u64 file_size) {
48 impl.status = DeliveryCacheProgressImpl::Status::Downloading;
49 impl.current_downloaded_bytes = 0;
50 impl.current_total_bytes = file_size;
51 std::memcpy(impl.current_directory.data(), dir_name.data(),
52 std::min<u64>(dir_name.size(), 0x31ull));
53 std::memcpy(impl.current_file.data(), file_name.data(),
54 std::min<u64>(file_name.size(), 0x31ull));
55 SignalUpdate();
56}
57
58void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
59 impl.current_downloaded_bytes = downloaded;
60 SignalUpdate();
61}
62
63void ProgressServiceBackend::FinishDownloadingFile() {
64 impl.total_downloaded_bytes += impl.current_total_bytes;
65 SignalUpdate();
66}
67
68void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
69 impl.status = DeliveryCacheProgressImpl::Status::Committing;
70 impl.current_file.fill(0);
71 impl.current_downloaded_bytes = 0;
72 impl.current_total_bytes = 0;
73 std::memcpy(impl.current_directory.data(), dir_name.data(),
74 std::min<u64>(dir_name.size(), 0x31ull));
75 SignalUpdate();
76}
77
78void ProgressServiceBackend::FinishDownload(ResultCode result) {
79 impl.total_downloaded_bytes = impl.total_bytes;
80 impl.status = DeliveryCacheProgressImpl::Status::Done;
81 impl.result = result;
82 SignalUpdate();
83}
84
85void ProgressServiceBackend::SignalUpdate() const {
86 if (need_hle_lock) {
87 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
88 event.writable->Signal();
89 } else {
90 event.writable->Signal();
91 }
92}
93
94Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
95
96Backend::~Backend() = default;
97
98NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {}
99
100NullBackend::~NullBackend() = default;
101
102bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
103 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
104 title.build_id);
105
106 progress.FinishDownload(RESULT_SUCCESS);
107 return true;
108}
109
110bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
111 ProgressServiceBackend& progress) {
112 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
113 title.build_id, name);
114
115 progress.FinishDownload(RESULT_SUCCESS);
116 return true;
117}
118
119bool NullBackend::Clear(u64 title_id) {
120 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
121
122 return true;
123}
124
125void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
126 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
127 Common::HexToString(passphrase));
128}
129
130std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
131 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
132 title.build_id);
133 return std::nullopt;
134}
135
136} // 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..3f5d8b5dd
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -0,0 +1,147 @@
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 "common/common_types.h"
10#include "core/file_sys/vfs_types.h"
11#include "core/hle/kernel/readable_event.h"
12#include "core/hle/kernel/writable_event.h"
13#include "core/hle/result.h"
14
15namespace Service::BCAT {
16
17struct DeliveryCacheProgressImpl;
18
19using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
20using Passphrase = std::array<u8, 0x20>;
21
22struct TitleIDVersion {
23 u64 title_id;
24 u64 build_id;
25};
26
27using DirectoryName = std::array<char, 0x20>;
28using FileName = std::array<char, 0x20>;
29
30struct DeliveryCacheProgressImpl {
31 enum class Status : s32 {
32 None = 0x0,
33 Queued = 0x1,
34 Connecting = 0x2,
35 ProcessingDataList = 0x3,
36 Downloading = 0x4,
37 Committing = 0x5,
38 Done = 0x9,
39 };
40
41 Status status;
42 ResultCode result = RESULT_SUCCESS;
43 DirectoryName current_directory;
44 FileName current_file;
45 s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
46 s64 current_total_bytes; ///< Bytes total on current file.
47 s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
48 s64 total_bytes; ///< Bytes total on overall download.
49 INSERT_PADDING_BYTES(
50 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
51};
52static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
53 "DeliveryCacheProgressImpl has incorrect size.");
54
55// A class to manage the signalling to the game about BCAT download progress.
56// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
57class ProgressServiceBackend {
58 friend class IBcatService;
59
60public:
61 // Clients should call this with true if any of the functions are going to be called from a
62 // non-HLE thread and this class need to lock the hle mutex. (default is false)
63 void SetNeedHLELock(bool need);
64
65 // Sets the number of bytes total in the entire download.
66 void SetTotalSize(u64 size);
67
68 // Notifies the application that the backend has started connecting to the server.
69 void StartConnecting();
70 // Notifies the application that the backend has begun accumulating and processing metadata.
71 void StartProcessingDataList();
72
73 // Notifies the application that a file is starting to be downloaded.
74 void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
75 // Updates the progress of the current file to the size passed.
76 void UpdateFileProgress(u64 downloaded);
77 // Notifies the application that the current file has completed download.
78 void FinishDownloadingFile();
79
80 // Notifies the application that all files in this directory have completed and are being
81 // finalized.
82 void CommitDirectory(std::string_view dir_name);
83
84 // Notifies the application that the operation completed with result code result.
85 void FinishDownload(ResultCode result);
86
87private:
88 explicit ProgressServiceBackend(std::string event_name);
89
90 Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
91 DeliveryCacheProgressImpl& GetImpl();
92
93 void SignalUpdate() const;
94
95 DeliveryCacheProgressImpl impl;
96 Kernel::EventPair event;
97 bool need_hle_lock = false;
98};
99
100// A class representing an abstract backend for BCAT functionality.
101class Backend {
102public:
103 explicit Backend(DirectoryGetter getter);
104 virtual ~Backend();
105
106 // Called when the backend is needed to synchronize the data for the game with title ID and
107 // version in title. A ProgressServiceBackend object is provided to alert the application of
108 // status.
109 virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
110 // Very similar to Synchronize, but only for the directory provided. Backends should not alter
111 // the data for any other directories.
112 virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
113 ProgressServiceBackend& progress) = 0;
114
115 // Removes all cached data associated with title id provided.
116 virtual bool Clear(u64 title_id) = 0;
117
118 // Sets the BCAT Passphrase to be used with the associated title ID.
119 virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
120
121 // Gets the launch parameter used by AM associated with the title ID and version provided.
122 virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
123
124protected:
125 DirectoryGetter dir_getter;
126};
127
128// A backend of BCAT that provides no operation.
129class NullBackend : public Backend {
130public:
131 explicit NullBackend(const DirectoryGetter& getter);
132 ~NullBackend() override;
133
134 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
135 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
136 ProgressServiceBackend& progress) override;
137
138 bool Clear(u64 title_id) override;
139
140 void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
141
142 std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
143};
144
145std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
146
147} // 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..e6ee0810b
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -0,0 +1,503 @@
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;
91constexpr 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& e) {
499 return StatusResult::ParseError;
500 }
501}
502
503} // 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..c2f946424 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -6,11 +6,15 @@
6 6
7namespace Service::BCAT { 7namespace Service::BCAT {
8 8
9BCAT::BCAT(std::shared_ptr<Module> module, const char* name) 9BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name)
10 : Module::Interface(std::move(module), name) { 10 : Module::Interface(std::move(module), fsc, name) {
11 // clang-format off
11 static const FunctionInfo functions[] = { 12 static const FunctionInfo functions[] = {
12 {0, &BCAT::CreateBcatService, "CreateBcatService"}, 13 {0, &BCAT::CreateBcatService, "CreateBcatService"},
14 {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
15 {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
13 }; 16 };
17 // clang-format on
14 RegisterHandlers(functions); 18 RegisterHandlers(functions);
15} 19}
16 20
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 802bd689a..813073658 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -10,7 +10,8 @@ namespace Service::BCAT {
10 10
11class BCAT final : public Module::Interface { 11class BCAT final : public Module::Interface {
12public: 12public:
13 explicit BCAT(std::shared_ptr<Module> module, const char* name); 13 explicit BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
14 const char* name);
14 ~BCAT() override; 15 ~BCAT() override;
15}; 16};
16 17
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index b7bd738fc..b3fed56c7 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -2,34 +2,254 @@
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() {
39 const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID();
40 u64 out{};
41 std::memcpy(&out, id.data(), sizeof(u64));
42 return out;
43}
44
45// The digest is only used to determine if a file is unique compared to others of the same name.
46// Since the algorithm isn't ever checked in game, MD5 is safe.
47BCATDigest DigestFile(const FileSys::VirtualFile& file) {
48 BCATDigest out{};
49 const auto bytes = file->ReadAllBytes();
50 mbedtls_md5(bytes.data(), bytes.size(), out.data());
51 return out;
52}
53
54// For a name to be valid it must be non-empty, must have a null terminating character as the final
55// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
56// file.
57bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
58 char match_char) {
59 const auto null_chars = std::count(name.begin(), name.end(), 0);
60 const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
61 return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
62 });
63 if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
64 LOG_ERROR(Service_BCAT, "Name passed was invalid!");
65 IPC::ResponseBuilder rb{ctx, 2};
66 rb.Push(ERROR_INVALID_ARGUMENT);
67 return false;
68 }
69
70 return true;
71}
72
73bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
74 return VerifyNameValidInternal(ctx, name, '-');
75}
76
77bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
78 return VerifyNameValidInternal(ctx, name, '.');
79}
80
81} // Anonymous namespace
82
83struct DeliveryCacheDirectoryEntry {
84 FileName name;
85 u64 size;
86 BCATDigest digest;
87};
88
89class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
90public:
91 IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
92 const DeliveryCacheProgressImpl& impl)
93 : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
94 // clang-format off
95 static const FunctionInfo functions[] = {
96 {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
97 {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
98 };
99 // clang-format on
100
101 RegisterHandlers(functions);
102 }
103
104private:
105 void GetEvent(Kernel::HLERequestContext& ctx) {
106 LOG_DEBUG(Service_BCAT, "called");
107
108 IPC::ResponseBuilder rb{ctx, 2, 1};
109 rb.Push(RESULT_SUCCESS);
110 rb.PushCopyObjects(event);
111 }
112
113 void GetImpl(Kernel::HLERequestContext& ctx) {
114 LOG_DEBUG(Service_BCAT, "called");
115
116 ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
117
118 IPC::ResponseBuilder rb{ctx, 2};
119 rb.Push(RESULT_SUCCESS);
120 }
121
122 Kernel::SharedPtr<Kernel::ReadableEvent> event;
123 const DeliveryCacheProgressImpl& impl;
124};
125
12class IBcatService final : public ServiceFramework<IBcatService> { 126class IBcatService final : public ServiceFramework<IBcatService> {
13public: 127public:
14 IBcatService() : ServiceFramework("IBcatService") { 128 IBcatService(Backend& backend) : ServiceFramework("IBcatService"), 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({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
167 progress.at(static_cast<std::size_t>(SyncType::Normal)));
168
169 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
170 rb.Push(RESULT_SUCCESS);
171 rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
172 }
173
174 void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
175 IPC::RequestParser rp{ctx};
176 const auto name_raw = rp.PopRaw<DirectoryName>();
177 const auto name =
178 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
179
180 LOG_DEBUG(Service_BCAT, "called, name={}", name);
181
182 backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
183 name,
184 progress.at(static_cast<std::size_t>(SyncType::Directory)));
185
186 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
187 rb.Push(RESULT_SUCCESS);
188 rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
189 }
190
191 void SetPassphrase(Kernel::HLERequestContext& ctx) {
192 IPC::RequestParser rp{ctx};
193 const auto title_id = rp.PopRaw<u64>();
194
195 const auto passphrase_raw = ctx.ReadBuffer();
196
197 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
198 Common::HexToString(passphrase_raw));
199
200 if (title_id == 0) {
201 LOG_ERROR(Service_BCAT, "Invalid title ID!");
202 IPC::ResponseBuilder rb{ctx, 2};
203 rb.Push(ERROR_INVALID_ARGUMENT);
204 }
205
206 if (passphrase_raw.size() > 0x40) {
207 LOG_ERROR(Service_BCAT, "Passphrase too large!");
208 IPC::ResponseBuilder rb{ctx, 2};
209 rb.Push(ERROR_INVALID_ARGUMENT);
210 return;
211 }
212
213 Passphrase passphrase{};
214 std::memcpy(passphrase.data(), passphrase_raw.data(),
215 std::min(passphrase.size(), passphrase_raw.size()));
216
217 backend.SetPassphrase(title_id, passphrase);
218
219 IPC::ResponseBuilder rb{ctx, 2};
220 rb.Push(RESULT_SUCCESS);
221 }
222
223 void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
224 IPC::RequestParser rp{ctx};
225 const auto title_id = rp.PopRaw<u64>();
226
227 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
228
229 if (title_id == 0) {
230 LOG_ERROR(Service_BCAT, "Invalid title ID!");
231 IPC::ResponseBuilder rb{ctx, 2};
232 rb.Push(ERROR_INVALID_ARGUMENT);
233 return;
234 }
235
236 if (!backend.Clear(title_id)) {
237 LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
238 IPC::ResponseBuilder rb{ctx, 2};
239 rb.Push(ERROR_FAILED_CLEAR_CACHE);
240 return;
241 }
242
243 IPC::ResponseBuilder rb{ctx, 2};
244 rb.Push(RESULT_SUCCESS);
245 }
246
247 Backend& backend;
248
249 std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
250 ProgressServiceBackend{"Normal"},
251 ProgressServiceBackend{"Directory"},
252 };
33}; 253};
34 254
35void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { 255void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
@@ -37,20 +257,331 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
37 257
38 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 258 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
39 rb.Push(RESULT_SUCCESS); 259 rb.Push(RESULT_SUCCESS);
40 rb.PushIpcInterface<IBcatService>(); 260 rb.PushIpcInterface<IBcatService>(*backend);
261}
262
263class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
264public:
265 IDeliveryCacheFileService(FileSys::VirtualDir root_)
266 : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
267 // clang-format off
268 static const FunctionInfo functions[] = {
269 {0, &IDeliveryCacheFileService::Open, "Open"},
270 {1, &IDeliveryCacheFileService::Read, "Read"},
271 {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
272 {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
273 };
274 // clang-format on
275
276 RegisterHandlers(functions);
277 }
278
279private:
280 void Open(Kernel::HLERequestContext& ctx) {
281 IPC::RequestParser rp{ctx};
282 const auto dir_name_raw = rp.PopRaw<DirectoryName>();
283 const auto file_name_raw = rp.PopRaw<FileName>();
284
285 const auto dir_name =
286 Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
287 const auto file_name =
288 Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
289
290 LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
291
292 if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
293 return;
294
295 if (current_file != nullptr) {
296 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
297 IPC::ResponseBuilder rb{ctx, 2};
298 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
299 return;
300 }
301
302 const auto dir = root->GetSubdirectory(dir_name);
303
304 if (dir == nullptr) {
305 LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
306 IPC::ResponseBuilder rb{ctx, 2};
307 rb.Push(ERROR_FAILED_OPEN_ENTITY);
308 return;
309 }
310
311 current_file = dir->GetFile(file_name);
312
313 if (current_file == nullptr) {
314 LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
315 IPC::ResponseBuilder rb{ctx, 2};
316 rb.Push(ERROR_FAILED_OPEN_ENTITY);
317 return;
318 }
319
320 IPC::ResponseBuilder rb{ctx, 2};
321 rb.Push(RESULT_SUCCESS);
322 }
323
324 void Read(Kernel::HLERequestContext& ctx) {
325 IPC::RequestParser rp{ctx};
326 const auto offset{rp.PopRaw<u64>()};
327
328 auto size = ctx.GetWriteBufferSize();
329
330 LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
331
332 if (current_file == nullptr) {
333 LOG_ERROR(Service_BCAT, "There is no file currently open!");
334 IPC::ResponseBuilder rb{ctx, 2};
335 rb.Push(ERROR_NO_OPEN_ENTITY);
336 }
337
338 size = std::min<u64>(current_file->GetSize() - offset, size);
339 const auto buffer = current_file->ReadBytes(size, offset);
340 ctx.WriteBuffer(buffer);
341
342 IPC::ResponseBuilder rb{ctx, 4};
343 rb.Push(RESULT_SUCCESS);
344 rb.Push<u64>(buffer.size());
345 }
346
347 void GetSize(Kernel::HLERequestContext& ctx) {
348 LOG_DEBUG(Service_BCAT, "called");
349
350 if (current_file == nullptr) {
351 LOG_ERROR(Service_BCAT, "There is no file currently open!");
352 IPC::ResponseBuilder rb{ctx, 2};
353 rb.Push(ERROR_NO_OPEN_ENTITY);
354 }
355
356 IPC::ResponseBuilder rb{ctx, 4};
357 rb.Push(RESULT_SUCCESS);
358 rb.Push<u64>(current_file->GetSize());
359 }
360
361 void GetDigest(Kernel::HLERequestContext& ctx) {
362 LOG_DEBUG(Service_BCAT, "called");
363
364 if (current_file == nullptr) {
365 LOG_ERROR(Service_BCAT, "There is no file currently open!");
366 IPC::ResponseBuilder rb{ctx, 2};
367 rb.Push(ERROR_NO_OPEN_ENTITY);
368 }
369
370 IPC::ResponseBuilder rb{ctx, 6};
371 rb.Push(RESULT_SUCCESS);
372 rb.PushRaw(DigestFile(current_file));
373 }
374
375 FileSys::VirtualDir root;
376 FileSys::VirtualFile current_file;
377};
378
379class IDeliveryCacheDirectoryService final
380 : public ServiceFramework<IDeliveryCacheDirectoryService> {
381public:
382 IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
383 : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
384 // clang-format off
385 static const FunctionInfo functions[] = {
386 {0, &IDeliveryCacheDirectoryService::Open, "Open"},
387 {1, &IDeliveryCacheDirectoryService::Read, "Read"},
388 {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
389 };
390 // clang-format on
391
392 RegisterHandlers(functions);
393 }
394
395private:
396 void Open(Kernel::HLERequestContext& ctx) {
397 IPC::RequestParser rp{ctx};
398 const auto name_raw = rp.PopRaw<DirectoryName>();
399 const auto name =
400 Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
401
402 LOG_DEBUG(Service_BCAT, "called, name={}", name);
403
404 if (!VerifyNameValidDir(ctx, name_raw))
405 return;
406
407 if (current_dir != nullptr) {
408 LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
409 IPC::ResponseBuilder rb{ctx, 2};
410 rb.Push(ERROR_ENTITY_ALREADY_OPEN);
411 return;
412 }
413
414 current_dir = root->GetSubdirectory(name);
415
416 if (current_dir == nullptr) {
417 LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
418 IPC::ResponseBuilder rb{ctx, 2};
419 rb.Push(ERROR_FAILED_OPEN_ENTITY);
420 return;
421 }
422
423 IPC::ResponseBuilder rb{ctx, 2};
424 rb.Push(RESULT_SUCCESS);
425 }
426
427 void Read(Kernel::HLERequestContext& ctx) {
428 auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
429
430 LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
431
432 if (current_dir == nullptr) {
433 LOG_ERROR(Service_BCAT, "There is no open directory!");
434 IPC::ResponseBuilder rb{ctx, 2};
435 rb.Push(ERROR_NO_OPEN_ENTITY);
436 return;
437 }
438
439 const auto files = current_dir->GetFiles();
440 write_size = std::min<u64>(write_size, files.size());
441 std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
442 std::transform(
443 files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
444 FileName name{};
445 std::memcpy(name.data(), file->GetName().data(),
446 std::min(file->GetName().size(), name.size()));
447 return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
448 });
449
450 ctx.WriteBuffer(entries);
451
452 IPC::ResponseBuilder rb{ctx, 3};
453 rb.Push(RESULT_SUCCESS);
454 rb.Push<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry));
455 }
456
457 void GetCount(Kernel::HLERequestContext& ctx) {
458 LOG_DEBUG(Service_BCAT, "called");
459
460 if (current_dir == nullptr) {
461 LOG_ERROR(Service_BCAT, "There is no open directory!");
462 IPC::ResponseBuilder rb{ctx, 2};
463 rb.Push(ERROR_NO_OPEN_ENTITY);
464 return;
465 }
466
467 const auto files = current_dir->GetFiles();
468
469 IPC::ResponseBuilder rb{ctx, 3};
470 rb.Push(RESULT_SUCCESS);
471 rb.Push<u32>(files.size());
472 }
473
474 FileSys::VirtualDir root;
475 FileSys::VirtualDir current_dir;
476};
477
478class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
479public:
480 IDeliveryCacheStorageService(FileSys::VirtualDir root_)
481 : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
482 // clang-format off
483 static const FunctionInfo functions[] = {
484 {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
485 {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
486 {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
487 };
488 // clang-format on
489
490 RegisterHandlers(functions);
491
492 for (const auto& subdir : root->GetSubdirectories()) {
493 DirectoryName name{};
494 std::memcpy(name.data(), subdir->GetName().data(),
495 std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
496 entries.push_back(name);
497 }
498 }
499
500private:
501 void CreateFileService(Kernel::HLERequestContext& ctx) {
502 LOG_DEBUG(Service_BCAT, "called");
503
504 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
505 rb.Push(RESULT_SUCCESS);
506 rb.PushIpcInterface<IDeliveryCacheFileService>(root);
507 }
508
509 void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
510 LOG_DEBUG(Service_BCAT, "called");
511
512 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
513 rb.Push(RESULT_SUCCESS);
514 rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
515 }
516
517 void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
518 auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
519
520 LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
521
522 size = std::min<u64>(size, entries.size() - next_read_index);
523 ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
524 next_read_index += size;
525
526 IPC::ResponseBuilder rb{ctx, 3};
527 rb.Push(RESULT_SUCCESS);
528 rb.Push<u32>(size);
529 }
530
531 FileSys::VirtualDir root;
532 std::vector<DirectoryName> entries;
533 u64 next_read_index = 0;
534};
535
536void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
537 LOG_DEBUG(Service_BCAT, "called");
538
539 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
540 rb.Push(RESULT_SUCCESS);
541 rb.PushIpcInterface<IDeliveryCacheStorageService>(
542 fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
543}
544
545void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
546 Kernel::HLERequestContext& ctx) {
547 IPC::RequestParser rp{ctx};
548 const auto title_id = rp.PopRaw<u64>();
549
550 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
551
552 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
553 rb.Push(RESULT_SUCCESS);
554 rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
555}
556
557std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
558 const auto backend = Settings::values.bcat_backend;
559
560#ifdef YUZU_ENABLE_BOXCAT
561 if (backend == "boxcat")
562 return std::make_unique<Boxcat>(std::move(getter));
563#endif
564
565 return std::make_unique<NullBackend>(std::move(getter));
41} 566}
42 567
43Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) 568Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
44 : ServiceFramework(name), module(std::move(module)) {} 569 const char* name)
570 : ServiceFramework(name), module(std::move(module)), fsc(fsc),
571 backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {}
45 572
46Module::Interface::~Interface() = default; 573Module::Interface::~Interface() = default;
47 574
48void InstallInterfaces(SM::ServiceManager& service_manager) { 575void InstallInterfaces(Core::System& system) {
49 auto module = std::make_shared<Module>(); 576 auto module = std::make_shared<Module>();
50 std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); 577 std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:a")
51 std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager); 578 ->InstallAsService(system.ServiceManager());
52 std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager); 579 std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m")
53 std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager); 580 ->InstallAsService(system.ServiceManager());
581 std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u")
582 ->InstallAsService(system.ServiceManager());
583 std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:s")
584 ->InstallAsService(system.ServiceManager());
54} 585}
55 586
56} // namespace Service::BCAT 587} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index f0d63cab0..27469926a 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -6,23 +6,39 @@
6 6
7#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
8 8
9namespace Service::BCAT { 9namespace Service {
10
11namespace FileSystem {
12class FileSystemController;
13} // namespace FileSystem
14
15namespace BCAT {
16
17class Backend;
10 18
11class Module final { 19class Module final {
12public: 20public:
13 class Interface : public ServiceFramework<Interface> { 21 class Interface : public ServiceFramework<Interface> {
14 public: 22 public:
15 explicit Interface(std::shared_ptr<Module> module, const char* name); 23 explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
24 const char* name);
16 ~Interface() override; 25 ~Interface() override;
17 26
18 void CreateBcatService(Kernel::HLERequestContext& ctx); 27 void CreateBcatService(Kernel::HLERequestContext& ctx);
28 void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
29 void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
19 30
20 protected: 31 protected:
32 FileSystem::FileSystemController& fsc;
33
21 std::shared_ptr<Module> module; 34 std::shared_ptr<Module> module;
35 std::unique_ptr<Backend> backend;
22 }; 36 };
23}; 37};
24 38
25/// Registers all BCAT services with the specified service manager. 39/// Registers all BCAT services with the specified service manager.
26void InstallInterfaces(SM::ServiceManager& service_manager); 40void InstallInterfaces(Core::System& system);
41
42} // namespace BCAT
27 43
28} // namespace Service::BCAT 44} // namespace Service
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 14cd0e322..7fa4e820b 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -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;
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3e0c03ec0..e6b49d8a2 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -110,6 +110,8 @@ public:
110 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; 110 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
111 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; 111 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
112 112
113 FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
114
113 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function 115 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
114 // above is called. 116 // above is called.
115 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); 117 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index f7a0aa4ff..a9cd119c4 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -165,12 +165,15 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
165 controller.battery_level[0] = BATTERY_FULL; 165 controller.battery_level[0] = BATTERY_FULL;
166 controller.battery_level[1] = BATTERY_FULL; 166 controller.battery_level[1] = BATTERY_FULL;
167 controller.battery_level[2] = BATTERY_FULL; 167 controller.battery_level[2] = BATTERY_FULL;
168 styleset_changed_events[controller_idx].writable->Signal();
168} 169}
169 170
170void Controller_NPad::OnInit() { 171void Controller_NPad::OnInit() {
171 auto& kernel = system.Kernel(); 172 auto& kernel = system.Kernel();
172 styleset_changed_event = Kernel::WritableEvent::CreateEventPair( 173 for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
173 kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged"); 174 styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
175 kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i));
176 }
174 177
175 if (!IsControllerActivated()) { 178 if (!IsControllerActivated()) {
176 return; 179 return;
@@ -431,7 +434,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
431 supported_npad_id_types.clear(); 434 supported_npad_id_types.clear();
432 supported_npad_id_types.resize(length / sizeof(u32)); 435 supported_npad_id_types.resize(length / sizeof(u32));
433 std::memcpy(supported_npad_id_types.data(), data, length); 436 std::memcpy(supported_npad_id_types.data(), data, length);
434 bool had_controller_update = false;
435 for (std::size_t i = 0; i < connected_controllers.size(); i++) { 437 for (std::size_t i = 0; i < connected_controllers.size(); i++) {
436 auto& controller = connected_controllers[i]; 438 auto& controller = connected_controllers[i];
437 if (!controller.is_connected) { 439 if (!controller.is_connected) {
@@ -450,10 +452,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
450 controller.type = requested_controller; 452 controller.type = requested_controller;
451 InitNewlyAddedControler(i); 453 InitNewlyAddedControler(i);
452 } 454 }
453 had_controller_update = true;
454 }
455 if (had_controller_update) {
456 styleset_changed_event.writable->Signal();
457 } 455 }
458 } 456 }
459} 457}
@@ -468,7 +466,6 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
468} 466}
469 467
470void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { 468void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
471 styleset_changed_event.writable->Signal();
472 hold_type = joy_hold_type; 469 hold_type = joy_hold_type;
473} 470}
474 471
@@ -479,7 +476,9 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
479void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { 476void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
480 const std::size_t npad_index = NPadIdToIndex(npad_id); 477 const std::size_t npad_index = NPadIdToIndex(npad_id);
481 ASSERT(npad_index < shared_memory_entries.size()); 478 ASSERT(npad_index < shared_memory_entries.size());
482 shared_memory_entries[npad_index].pad_assignment = assignment_mode; 479 if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
480 shared_memory_entries[npad_index].pad_assignment = assignment_mode;
481 }
483} 482}
484 483
485void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, 484void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
@@ -498,11 +497,12 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
498 last_processed_vibration = vibrations.back(); 497 last_processed_vibration = vibrations.back();
499} 498}
500 499
501Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent() const { 500Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(
501 u32 npad_id) const {
502 // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should 502 // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
503 // be signalled at least once, and signaled after a new controller is connected? 503 // be signalled at least once, and signaled after a new controller is connected?
504 styleset_changed_event.writable->Signal(); 504 const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
505 return styleset_changed_event.readable; 505 return styleset_event.readable;
506} 506}
507 507
508Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { 508Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index f72a9900c..1bc3d55d6 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -109,7 +109,7 @@ public:
109 void VibrateController(const std::vector<u32>& controller_ids, 109 void VibrateController(const std::vector<u32>& controller_ids,
110 const std::vector<Vibration>& vibrations); 110 const std::vector<Vibration>& vibrations);
111 111
112 Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent() const; 112 Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
113 Vibration GetLastVibration() const; 113 Vibration GetLastVibration() const;
114 114
115 void AddNewController(NPadControllerType controller); 115 void AddNewController(NPadControllerType controller);
@@ -315,7 +315,8 @@ private:
315 sticks; 315 sticks;
316 std::vector<u32> supported_npad_id_types{}; 316 std::vector<u32> supported_npad_id_types{};
317 NpadHoldType hold_type{NpadHoldType::Vertical}; 317 NpadHoldType hold_type{NpadHoldType::Vertical};
318 Kernel::EventPair styleset_changed_event; 318 // Each controller should have their own styleset changed event
319 std::array<Kernel::EventPair, 10> styleset_changed_events;
319 Vibration last_processed_vibration{}; 320 Vibration last_processed_vibration{};
320 std::array<ControllerHolder, 10> connected_controllers{}; 321 std::array<ControllerHolder, 10> connected_controllers{};
321 bool can_controllers_vibrate{true}; 322 bool can_controllers_vibrate{true};
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 33145b891..8d76ba746 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -480,7 +480,7 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
480 IPC::ResponseBuilder rb{ctx, 2, 1}; 480 IPC::ResponseBuilder rb{ctx, 2, 1};
481 rb.Push(RESULT_SUCCESS); 481 rb.Push(RESULT_SUCCESS);
482 rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) 482 rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
483 .GetStyleSetChangedEvent()); 483 .GetStyleSetChangedEvent(npad_id));
484} 484}
485 485
486void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { 486void Hid::DisconnectNpad(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/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 5b8248433..1b52511a5 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -9,6 +9,7 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/swap.h" 10#include "common/swap.h"
11#include "core/hle/service/nvdrv/nvdata.h" 11#include "core/hle/service/nvdrv/nvdata.h"
12#include "core/hle/service/service.h"
12 13
13namespace Core { 14namespace Core {
14class System; 15class System;
@@ -38,8 +39,9 @@ public:
38 * @param output A buffer where the output data will be written to. 39 * @param output A buffer where the output data will be written to.
39 * @returns The result code of the ioctl. 40 * @returns The result code of the ioctl.
40 */ 41 */
41 virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 42 virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
42 IoctlCtrl& ctrl) = 0; 43 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
44 IoctlVersion version) = 0;
43 45
44protected: 46protected:
45 Core::System& system; 47 Core::System& system;
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 926a1285d..f764388bc 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -17,8 +17,9 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de
17 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} 17 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
18nvdisp_disp0 ::~nvdisp_disp0() = default; 18nvdisp_disp0 ::~nvdisp_disp0() = default;
19 19
20u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 20u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
21 IoctlCtrl& ctrl) { 21 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
22 IoctlVersion version) {
22 UNIMPLEMENTED_MSG("Unimplemented ioctl"); 23 UNIMPLEMENTED_MSG("Unimplemented ioctl");
23 return 0; 24 return 0;
24} 25}
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index e79e490ff..6fcdeee84 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -20,8 +20,9 @@ public:
20 explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); 20 explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
21 ~nvdisp_disp0() override; 21 ~nvdisp_disp0() override;
22 22
23 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 23 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
24 IoctlCtrl& ctrl) override; 24 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
25 IoctlVersion version) override;
25 26
26 /// Performs a screen flip, drawing the buffer pointed to by the handle. 27 /// Performs a screen flip, drawing the buffer pointed to by the handle.
27 void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, 28 void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
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 24ab3f2e9..6bc053f27 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -26,8 +26,9 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_
26 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} 26 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
27nvhost_as_gpu::~nvhost_as_gpu() = default; 27nvhost_as_gpu::~nvhost_as_gpu() = default;
28 28
29u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 29u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
30 IoctlCtrl& ctrl) { 30 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
31 IoctlVersion version) {
31 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 32 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
32 command.raw, input.size(), output.size()); 33 command.raw, input.size(), output.size());
33 34
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 30ca5f4c3..169fb8f0e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -20,8 +20,9 @@ public:
20 explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); 20 explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
21 ~nvhost_as_gpu() override; 21 ~nvhost_as_gpu() override;
22 22
23 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 23 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
24 IoctlCtrl& ctrl) override; 24 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
25 IoctlVersion version) override;
25 26
26private: 27private:
27 enum class IoctlCommand : u32_le { 28 enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 9a66a5f88..ff6b1abae 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -19,8 +19,9 @@ nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
19 : nvdevice(system), events_interface{events_interface} {} 19 : nvdevice(system), events_interface{events_interface} {}
20nvhost_ctrl::~nvhost_ctrl() = default; 20nvhost_ctrl::~nvhost_ctrl() = default;
21 21
22u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 22u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
23 IoctlCtrl& ctrl) { 23 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
24 IoctlVersion version) {
24 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 25 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
25 command.raw, input.size(), output.size()); 26 command.raw, input.size(), output.size());
26 27
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 14e6e7e57..9898623de 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -17,8 +17,9 @@ public:
17 explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface); 17 explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
18 ~nvhost_ctrl() override; 18 ~nvhost_ctrl() override;
19 19
20 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 20 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
21 IoctlCtrl& ctrl) override; 21 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
22 IoctlVersion version) override;
22 23
23private: 24private:
24 enum class IoctlCommand : u32_le { 25 enum class IoctlCommand : u32_le {
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 988effd90..389ace76f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -15,14 +15,15 @@ namespace Service::Nvidia::Devices {
15nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {} 15nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
16nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; 16nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
17 17
18u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 18u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
19 IoctlCtrl& ctrl) { 19 const std::vector<u8>& input2, std::vector<u8>& output,
20 std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) {
20 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 21 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
21 command.raw, input.size(), output.size()); 22 command.raw, input.size(), output.size());
22 23
23 switch (static_cast<IoctlCommand>(command.raw)) { 24 switch (static_cast<IoctlCommand>(command.raw)) {
24 case IoctlCommand::IocGetCharacteristicsCommand: 25 case IoctlCommand::IocGetCharacteristicsCommand:
25 return GetCharacteristics(input, output); 26 return GetCharacteristics(input, output, output2, version);
26 case IoctlCommand::IocGetTPCMasksCommand: 27 case IoctlCommand::IocGetTPCMasksCommand:
27 return GetTPCMasks(input, output); 28 return GetTPCMasks(input, output);
28 case IoctlCommand::IocGetActiveSlotMaskCommand: 29 case IoctlCommand::IocGetActiveSlotMaskCommand:
@@ -44,7 +45,8 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec
44 return 0; 45 return 0;
45} 46}
46 47
47u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) { 48u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
49 std::vector<u8>& output2, IoctlVersion version) {
48 LOG_DEBUG(Service_NVDRV, "called"); 50 LOG_DEBUG(Service_NVDRV, "called");
49 IoctlCharacteristics params{}; 51 IoctlCharacteristics params{};
50 std::memcpy(&params, input.data(), input.size()); 52 std::memcpy(&params, input.data(), input.size());
@@ -85,7 +87,13 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto
85 params.gc.gr_compbit_store_base_hw = 0x0; 87 params.gc.gr_compbit_store_base_hw = 0x0;
86 params.gpu_characteristics_buf_size = 0xA0; 88 params.gpu_characteristics_buf_size = 0xA0;
87 params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) 89 params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
88 std::memcpy(output.data(), &params, output.size()); 90
91 if (version == IoctlVersion::Version3) {
92 std::memcpy(output.data(), input.data(), output.size());
93 std::memcpy(output2.data(), &params.gc, output2.size());
94 } else {
95 std::memcpy(output.data(), &params, output.size());
96 }
89 return 0; 97 return 0;
90} 98}
91 99
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 2b035ae3f..642b0a2cb 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -16,8 +16,9 @@ public:
16 explicit nvhost_ctrl_gpu(Core::System& system); 16 explicit nvhost_ctrl_gpu(Core::System& system);
17 ~nvhost_ctrl_gpu() override; 17 ~nvhost_ctrl_gpu() override;
18 18
19 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 19 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
20 IoctlCtrl& ctrl) override; 20 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
21 IoctlVersion version) override;
21 22
22private: 23private:
23 enum class IoctlCommand : u32_le { 24 enum class IoctlCommand : u32_le {
@@ -162,7 +163,8 @@ private:
162 }; 163 };
163 static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size"); 164 static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size");
164 165
165 u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); 166 u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
167 std::vector<u8>& output2, IoctlVersion version);
166 u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); 168 u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
167 u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); 169 u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
168 u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); 170 u32 ZCullGetCtxSize(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 b4ee2a255..2b8d1bef6 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -17,8 +17,9 @@ nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
17 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} 17 : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
18nvhost_gpu::~nvhost_gpu() = default; 18nvhost_gpu::~nvhost_gpu() = default;
19 19
20u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 20u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
21 IoctlCtrl& ctrl) { 21 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
22 IoctlVersion version) {
22 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 23 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
23 command.raw, input.size(), output.size()); 24 command.raw, input.size(), output.size());
24 25
@@ -50,7 +51,7 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u
50 return SubmitGPFIFO(input, output); 51 return SubmitGPFIFO(input, output);
51 } 52 }
52 if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) { 53 if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) {
53 return KickoffPB(input, output); 54 return KickoffPB(input, output, input2, version);
54 } 55 }
55 } 56 }
56 57
@@ -173,7 +174,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
173 return 0; 174 return 0;
174} 175}
175 176
176u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) { 177u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
178 const std::vector<u8>& input2, IoctlVersion version) {
177 if (input.size() < sizeof(IoctlSubmitGpfifo)) { 179 if (input.size() < sizeof(IoctlSubmitGpfifo)) {
178 UNIMPLEMENTED(); 180 UNIMPLEMENTED();
179 } 181 }
@@ -183,9 +185,13 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
183 params.num_entries, params.flags.raw); 185 params.num_entries, params.flags.raw);
184 186
185 Tegra::CommandList entries(params.num_entries); 187 Tegra::CommandList entries(params.num_entries);
186 Memory::ReadBlock(params.address, entries.data(), 188 if (version == IoctlVersion::Version2) {
187 params.num_entries * sizeof(Tegra::CommandListHeader)); 189 std::memcpy(entries.data(), input2.data(),
188 190 params.num_entries * sizeof(Tegra::CommandListHeader));
191 } else {
192 Memory::ReadBlock(params.address, entries.data(),
193 params.num_entries * sizeof(Tegra::CommandListHeader));
194 }
189 UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); 195 UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
190 UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); 196 UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
191 197
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index d2e8fbae9..d056dd046 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -24,8 +24,9 @@ public:
24 explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); 24 explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
25 ~nvhost_gpu() override; 25 ~nvhost_gpu() override;
26 26
27 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 27 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
28 IoctlCtrl& ctrl) override; 28 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
29 IoctlVersion version) override;
29 30
30private: 31private:
31 enum class IoctlCommand : u32_le { 32 enum class IoctlCommand : u32_le {
@@ -183,7 +184,8 @@ private:
183 u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); 184 u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
184 u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); 185 u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
185 u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); 186 u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
186 u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output); 187 u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
188 const std::vector<u8>& input2, IoctlVersion version);
187 u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); 189 u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
188 u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); 190 u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
189 191
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index f572ad30f..bdae8b887 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
13nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {} 13nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
14nvhost_nvdec::~nvhost_nvdec() = default; 14nvhost_nvdec::~nvhost_nvdec() = default;
15 15
16u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 16u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
17 IoctlCtrl& ctrl) { 17 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
18 IoctlVersion version) {
18 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 19 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
19 command.raw, input.size(), output.size()); 20 command.raw, input.size(), output.size());
20 21
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index 2710f0511..cbdac8069 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -16,8 +16,9 @@ public:
16 explicit nvhost_nvdec(Core::System& system); 16 explicit nvhost_nvdec(Core::System& system);
17 ~nvhost_nvdec() override; 17 ~nvhost_nvdec() override;
18 18
19 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 19 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
20 IoctlCtrl& ctrl) override; 20 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
21 IoctlVersion version) override;
21 22
22private: 23private:
23 enum class IoctlCommand : u32_le { 24 enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index 38282956f..96e7b7dab 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
13nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} 13nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
14nvhost_nvjpg::~nvhost_nvjpg() = default; 14nvhost_nvjpg::~nvhost_nvjpg() = default;
15 15
16u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 16u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
17 IoctlCtrl& ctrl) { 17 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
18 IoctlVersion version) {
18 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 19 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
19 command.raw, input.size(), output.size()); 20 command.raw, input.size(), output.size());
20 21
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 379766693..98dcac52f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -16,8 +16,9 @@ public:
16 explicit nvhost_nvjpg(Core::System& system); 16 explicit nvhost_nvjpg(Core::System& system);
17 ~nvhost_nvjpg() override; 17 ~nvhost_nvjpg() override;
18 18
19 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 19 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
20 IoctlCtrl& ctrl) override; 20 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
21 IoctlVersion version) override;
21 22
22private: 23private:
23 enum class IoctlCommand : u32_le { 24 enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index 70e8091db..c695b8863 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices {
13nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {} 13nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
14nvhost_vic::~nvhost_vic() = default; 14nvhost_vic::~nvhost_vic() = default;
15 15
16u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 16u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
17 IoctlCtrl& ctrl) { 17 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
18 IoctlVersion version) {
18 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", 19 LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
19 command.raw, input.size(), output.size()); 20 command.raw, input.size(), output.size());
20 21
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index 7d111977e..bec32bea1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -16,8 +16,9 @@ public:
16 explicit nvhost_vic(Core::System& system); 16 explicit nvhost_vic(Core::System& system);
17 ~nvhost_vic() override; 17 ~nvhost_vic() override;
18 18
19 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 19 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
20 IoctlCtrl& ctrl) override; 20 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
21 IoctlVersion version) override;
21 22
22private: 23private:
23 enum class IoctlCommand : u32_le { 24 enum class IoctlCommand : u32_le {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 223b496b7..8c742316c 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -28,8 +28,9 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
28 return object->addr; 28 return object->addr;
29} 29}
30 30
31u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 31u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
32 IoctlCtrl& ctrl) { 32 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
33 IoctlVersion version) {
33 switch (static_cast<IoctlCommand>(command.raw)) { 34 switch (static_cast<IoctlCommand>(command.raw)) {
34 case IoctlCommand::Create: 35 case IoctlCommand::Create:
35 return IocCreate(input, output); 36 return IocCreate(input, output);
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index bf4a101c2..73c2e8809 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -22,8 +22,9 @@ public:
22 /// Returns the allocated address of an nvmap object given its handle. 22 /// Returns the allocated address of an nvmap object given its handle.
23 VAddr GetObjectAddress(u32 handle) const; 23 VAddr GetObjectAddress(u32 handle) const;
24 24
25 u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, 25 u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
26 IoctlCtrl& ctrl) override; 26 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
27 IoctlVersion version) override;
27 28
28 /// Represents an nvmap object. 29 /// Represents an nvmap object.
29 struct Object { 30 struct Object {
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index d5be64ed2..5e0c23602 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -33,42 +33,77 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) {
33 rb.Push<u32>(0); 33 rb.Push<u32>(0);
34} 34}
35 35
36void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { 36void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) {
37 LOG_DEBUG(Service_NVDRV, "called");
38
39 IPC::RequestParser rp{ctx}; 37 IPC::RequestParser rp{ctx};
40 u32 fd = rp.Pop<u32>(); 38 u32 fd = rp.Pop<u32>();
41 u32 command = rp.Pop<u32>(); 39 u32 command = rp.Pop<u32>();
42 40
43 std::vector<u8> output(ctx.GetWriteBufferSize()); 41 /// Ioctl 3 has 2 outputs, first in the input params, second is the result
42 std::vector<u8> output(ctx.GetWriteBufferSize(0));
43 std::vector<u8> output2;
44 if (version == IoctlVersion::Version3) {
45 output2.resize((ctx.GetWriteBufferSize(1)));
46 }
47
48 /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer.
49 /// KickOfPB uses this
50 auto input = ctx.ReadBuffer(0);
51
52 std::vector<u8> input2;
53 if (version == IoctlVersion::Version2) {
54 input2 = ctx.ReadBuffer(1);
55 }
44 56
45 IoctlCtrl ctrl{}; 57 IoctlCtrl ctrl{};
46 58
47 u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output, ctrl); 59 u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version);
48 60
49 if (ctrl.must_delay) { 61 if (ctrl.must_delay) {
50 ctrl.fresh_call = false; 62 ctrl.fresh_call = false;
51 ctx.SleepClientThread( 63 ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout,
52 "NVServices::DelayedResponse", ctrl.timeout, 64 [=](Kernel::SharedPtr<Kernel::Thread> thread,
53 [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, 65 Kernel::HLERequestContext& ctx,
54 Kernel::ThreadWakeupReason reason) { 66 Kernel::ThreadWakeupReason reason) {
55 IoctlCtrl ctrl2{ctrl}; 67 IoctlCtrl ctrl2{ctrl};
56 std::vector<u8> output2 = output; 68 std::vector<u8> tmp_output = output;
57 u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output2, ctrl2); 69 std::vector<u8> tmp_output2 = output2;
58 ctx.WriteBuffer(output2); 70 u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
59 IPC::ResponseBuilder rb{ctx, 3}; 71 tmp_output2, ctrl2, version);
60 rb.Push(RESULT_SUCCESS); 72 ctx.WriteBuffer(tmp_output, 0);
61 rb.Push(result); 73 if (version == IoctlVersion::Version3) {
62 }, 74 ctx.WriteBuffer(tmp_output2, 1);
63 nvdrv->GetEventWriteable(ctrl.event_id)); 75 }
76 IPC::ResponseBuilder rb{ctx, 3};
77 rb.Push(RESULT_SUCCESS);
78 rb.Push(result);
79 },
80 nvdrv->GetEventWriteable(ctrl.event_id));
64 } else { 81 } else {
65 ctx.WriteBuffer(output); 82 ctx.WriteBuffer(output);
83 if (version == IoctlVersion::Version3) {
84 ctx.WriteBuffer(output2, 1);
85 }
66 } 86 }
67 IPC::ResponseBuilder rb{ctx, 3}; 87 IPC::ResponseBuilder rb{ctx, 3};
68 rb.Push(RESULT_SUCCESS); 88 rb.Push(RESULT_SUCCESS);
69 rb.Push(result); 89 rb.Push(result);
70} 90}
71 91
92void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
93 LOG_DEBUG(Service_NVDRV, "called");
94 IoctlBase(ctx, IoctlVersion::Version1);
95}
96
97void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
98 LOG_DEBUG(Service_NVDRV, "called");
99 IoctlBase(ctx, IoctlVersion::Version2);
100}
101
102void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
103 LOG_DEBUG(Service_NVDRV, "called");
104 IoctlBase(ctx, IoctlVersion::Version3);
105}
106
72void NVDRV::Close(Kernel::HLERequestContext& ctx) { 107void NVDRV::Close(Kernel::HLERequestContext& ctx) {
73 LOG_DEBUG(Service_NVDRV, "called"); 108 LOG_DEBUG(Service_NVDRV, "called");
74 109
@@ -154,8 +189,8 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
154 {8, &NVDRV::SetClientPID, "SetClientPID"}, 189 {8, &NVDRV::SetClientPID, "SetClientPID"},
155 {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, 190 {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"},
156 {10, nullptr, "InitializeDevtools"}, 191 {10, nullptr, "InitializeDevtools"},
157 {11, &NVDRV::Ioctl, "Ioctl2"}, 192 {11, &NVDRV::Ioctl2, "Ioctl2"},
158 {12, nullptr, "Ioctl3"}, 193 {12, &NVDRV::Ioctl3, "Ioctl3"},
159 {13, &NVDRV::FinishInitialize, "FinishInitialize"}, 194 {13, &NVDRV::FinishInitialize, "FinishInitialize"},
160 }; 195 };
161 RegisterHandlers(functions); 196 RegisterHandlers(functions);
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 10a0ecd52..9269ce00c 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -24,6 +24,8 @@ public:
24private: 24private:
25 void Open(Kernel::HLERequestContext& ctx); 25 void Open(Kernel::HLERequestContext& ctx);
26 void Ioctl(Kernel::HLERequestContext& ctx); 26 void Ioctl(Kernel::HLERequestContext& ctx);
27 void Ioctl2(Kernel::HLERequestContext& ctx);
28 void Ioctl3(Kernel::HLERequestContext& ctx);
27 void Close(Kernel::HLERequestContext& ctx); 29 void Close(Kernel::HLERequestContext& ctx);
28 void Initialize(Kernel::HLERequestContext& ctx); 30 void Initialize(Kernel::HLERequestContext& ctx);
29 void QueryEvent(Kernel::HLERequestContext& ctx); 31 void QueryEvent(Kernel::HLERequestContext& ctx);
@@ -31,6 +33,7 @@ private:
31 void FinishInitialize(Kernel::HLERequestContext& ctx); 33 void FinishInitialize(Kernel::HLERequestContext& ctx);
32 void GetStatus(Kernel::HLERequestContext& ctx); 34 void GetStatus(Kernel::HLERequestContext& ctx);
33 void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); 35 void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx);
36 void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version);
34 37
35 std::shared_ptr<Module> nvdrv; 38 std::shared_ptr<Module> nvdrv;
36 39
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index ac03cbc23..529b03471 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -34,6 +34,12 @@ enum class EventState {
34 Busy = 3, 34 Busy = 3,
35}; 35};
36 36
37enum class IoctlVersion : u32 {
38 Version1,
39 Version2,
40 Version3,
41};
42
37struct IoctlCtrl { 43struct IoctlCtrl {
38 // First call done to the servioce for services that call itself again after a call. 44 // First call done to the servioce for services that call itself again after a call.
39 bool fresh_call{true}; 45 bool fresh_call{true};
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 2011a226a..307a7e928 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -71,13 +71,14 @@ u32 Module::Open(const std::string& device_name) {
71 return fd; 71 return fd;
72} 72}
73 73
74u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output, 74u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
75 IoctlCtrl& ctrl) { 75 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
76 IoctlVersion version) {
76 auto itr = open_files.find(fd); 77 auto itr = open_files.find(fd);
77 ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); 78 ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
78 79
79 auto& device = itr->second; 80 auto& device = itr->second;
80 return device->ioctl({command}, input, output, ctrl); 81 return device->ioctl({command}, input, input2, output, output2, ctrl, version);
81} 82}
82 83
83ResultCode Module::Close(u32 fd) { 84ResultCode Module::Close(u32 fd) {
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index a339ab672..f8bb28969 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -106,8 +106,9 @@ public:
106 /// Opens a device node and returns a file descriptor to it. 106 /// Opens a device node and returns a file descriptor to it.
107 u32 Open(const std::string& device_name); 107 u32 Open(const std::string& device_name);
108 /// Sends an ioctl command to the specified file descriptor. 108 /// Sends an ioctl command to the specified file descriptor.
109 u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output, 109 u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
110 IoctlCtrl& ctrl); 110 std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
111 IoctlVersion version);
111 /// Closes a device file descriptor and returns operation success. 112 /// Closes a device file descriptor and returns operation success.
112 ResultCode Close(u32 fd); 113 ResultCode Close(u32 fd);
113 114
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 831a427de..f2c6fe9dc 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
208 AOC::InstallInterfaces(*sm, system); 208 AOC::InstallInterfaces(*sm, system);
209 APM::InstallInterfaces(system); 209 APM::InstallInterfaces(system);
210 Audio::InstallInterfaces(*sm, system); 210 Audio::InstallInterfaces(*sm, system);
211 BCAT::InstallInterfaces(*sm); 211 BCAT::InstallInterfaces(system);
212 BPC::InstallInterfaces(*sm); 212 BPC::InstallInterfaces(*sm);
213 BtDrv::InstallInterfaces(*sm, system); 213 BtDrv::InstallInterfaces(*sm, system);
214 BTM::InstallInterfaces(*sm, system); 214 BTM::InstallInterfaces(*sm, system);
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index e75c700ad..f629892ae 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -150,6 +150,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
150 // Apply cheats if they exist and the program has a valid title ID 150 // Apply cheats if they exist and the program has a valid title ID
151 if (pm) { 151 if (pm) {
152 auto& system = Core::System::GetInstance(); 152 auto& system = Core::System::GetInstance();
153 system.SetCurrentProcessBuildID(nso_header.build_id);
153 const auto cheats = pm->CreateCheatList(system, nso_header.build_id); 154 const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
154 if (!cheats.empty()) { 155 if (!cheats.empty()) {
155 system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); 156 system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
diff --git a/src/core/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/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 28272ef6f..7a6355ce2 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -544,7 +544,7 @@ enum class VoteOperation : u64 {
544 Eq = 2, // allThreadsEqualNV 544 Eq = 2, // allThreadsEqualNV
545}; 545};
546 546
547enum class ImageAtomicSize : u64 { 547enum class ImageAtomicOperationType : u64 {
548 U32 = 0, 548 U32 = 0,
549 S32 = 1, 549 S32 = 1,
550 U64 = 2, 550 U64 = 2,
@@ -1432,11 +1432,11 @@ union Instruction {
1432 ASSERT(mode == SurfaceDataMode::D_BA); 1432 ASSERT(mode == SurfaceDataMode::D_BA);
1433 return store_data_layout; 1433 return store_data_layout;
1434 } 1434 }
1435 } sust; 1435 } suldst;
1436 1436
1437 union { 1437 union {
1438 BitField<28, 1, u64> is_ba; 1438 BitField<28, 1, u64> is_ba;
1439 BitField<51, 3, ImageAtomicSize> size; 1439 BitField<51, 3, ImageAtomicOperationType> operation_type;
1440 BitField<33, 3, ImageType> image_type; 1440 BitField<33, 3, ImageType> image_type;
1441 BitField<29, 4, ImageAtomicOperation> operation; 1441 BitField<29, 4, ImageAtomicOperation> operation;
1442 BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; 1442 BitField<49, 2, OutOfBoundsStore> out_of_bounds_store;
@@ -1595,6 +1595,7 @@ public:
1595 TMML_B, // Texture Mip Map Level 1595 TMML_B, // Texture Mip Map Level
1596 TMML, // Texture Mip Map Level 1596 TMML, // Texture Mip Map Level
1597 SUST, // Surface Store 1597 SUST, // Surface Store
1598 SULD, // Surface Load
1598 SUATOM, // Surface Atomic Operation 1599 SUATOM, // Surface Atomic Operation
1599 EXIT, 1600 EXIT,
1600 NOP, 1601 NOP,
@@ -1884,6 +1885,7 @@ private:
1884 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), 1885 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
1885 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), 1886 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
1886 INST("11101011001-----", Id::SUST, Type::Image, "SUST"), 1887 INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
1888 INST("11101011000-----", Id::SULD, Type::Image, "SULD"),
1887 INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"), 1889 INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),
1888 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), 1890 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"),
1889 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), 1891 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 4f59a87b4..64de7e425 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -2,8 +2,10 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
5#include <array> 6#include <array>
6#include <cstddef> 7#include <cstddef>
8#include <vector>
7#include <glad/glad.h> 9#include <glad/glad.h>
8 10
9#include "common/logging/log.h" 11#include "common/logging/log.h"
@@ -30,9 +32,27 @@ bool TestProgram(const GLchar* glsl) {
30 return link_status == GL_TRUE; 32 return link_status == GL_TRUE;
31} 33}
32 34
35std::vector<std::string_view> GetExtensions() {
36 GLint num_extensions;
37 glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
38 std::vector<std::string_view> extensions;
39 extensions.reserve(num_extensions);
40 for (GLint index = 0; index < num_extensions; ++index) {
41 extensions.push_back(
42 reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index))));
43 }
44 return extensions;
45}
46
47bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) {
48 return std::find(images.begin(), images.end(), extension) != images.end();
49}
50
33} // Anonymous namespace 51} // Anonymous namespace
34 52
35Device::Device() { 53Device::Device() {
54 const std::vector extensions = GetExtensions();
55
36 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); 56 uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
37 shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); 57 shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
38 max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); 58 max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
@@ -40,6 +60,7 @@ Device::Device() {
40 has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && 60 has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
41 GLAD_GL_NV_shader_thread_shuffle; 61 GLAD_GL_NV_shader_thread_shuffle;
42 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; 62 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
63 has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
43 has_variable_aoffi = TestVariableAoffi(); 64 has_variable_aoffi = TestVariableAoffi();
44 has_component_indexing_bug = TestComponentIndexingBug(); 65 has_component_indexing_bug = TestComponentIndexingBug();
45 has_precise_bug = TestPreciseBug(); 66 has_precise_bug = TestPreciseBug();
@@ -55,6 +76,7 @@ Device::Device(std::nullptr_t) {
55 max_varyings = 15; 76 max_varyings = 15;
56 has_warp_intrinsics = true; 77 has_warp_intrinsics = true;
57 has_vertex_viewport_layer = true; 78 has_vertex_viewport_layer = true;
79 has_image_load_formatted = true;
58 has_variable_aoffi = true; 80 has_variable_aoffi = true;
59 has_component_indexing_bug = false; 81 has_component_indexing_bug = false;
60 has_precise_bug = false; 82 has_precise_bug = false;
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index ba6dcd3be..bb273c3d6 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -38,6 +38,10 @@ public:
38 return has_vertex_viewport_layer; 38 return has_vertex_viewport_layer;
39 } 39 }
40 40
41 bool HasImageLoadFormatted() const {
42 return has_image_load_formatted;
43 }
44
41 bool HasVariableAoffi() const { 45 bool HasVariableAoffi() const {
42 return has_variable_aoffi; 46 return has_variable_aoffi;
43 } 47 }
@@ -61,6 +65,7 @@ private:
61 u32 max_varyings{}; 65 u32 max_varyings{};
62 bool has_warp_intrinsics{}; 66 bool has_warp_intrinsics{};
63 bool has_vertex_viewport_layer{}; 67 bool has_vertex_viewport_layer{};
68 bool has_image_load_formatted{};
64 bool has_variable_aoffi{}; 69 bool has_variable_aoffi{};
65 bool has_component_indexing_bug{}; 70 bool has_component_indexing_bug{};
66 bool has_precise_bug{}; 71 bool has_precise_bug{};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 0dbc4c02f..42ca3b1bd 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -211,14 +211,14 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
211 const auto primitive_mode{variant.primitive_mode}; 211 const auto primitive_mode{variant.primitive_mode};
212 const auto texture_buffer_usage{variant.texture_buffer_usage}; 212 const auto texture_buffer_usage{variant.texture_buffer_usage};
213 213
214 std::string source = "#version 430 core\n" 214 std::string source = R"(#version 430 core
215 "#extension GL_ARB_separate_shader_objects : enable\n" 215#extension GL_ARB_separate_shader_objects : enable
216 "#extension GL_NV_gpu_shader5 : enable\n" 216#extension GL_ARB_shader_viewport_layer_array : enable
217 "#extension GL_NV_shader_thread_group : enable\n" 217#extension GL_EXT_shader_image_load_formatted : enable
218 "#extension GL_NV_shader_thread_shuffle : enable\n"; 218#extension GL_NV_gpu_shader5 : enable
219 if (entries.shader_viewport_layer_array) { 219#extension GL_NV_shader_thread_group : enable
220 source += "#extension GL_ARB_shader_viewport_layer_array : enable\n"; 220#extension GL_NV_shader_thread_shuffle : enable
221 } 221)";
222 if (program_type == ProgramType::Compute) { 222 if (program_type == ProgramType::Compute) {
223 source += "#extension GL_ARB_compute_variable_group_size : require\n"; 223 source += "#extension GL_ARB_compute_variable_group_size : require\n";
224 } 224 }
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 74cb59bc1..8fa9e6534 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -19,6 +19,7 @@
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/node.h"
22#include "video_core/shader/shader_ir.h" 23#include "video_core/shader/shader_ir.h"
23 24
24namespace OpenGL::GLShader { 25namespace OpenGL::GLShader {
@@ -241,6 +242,26 @@ constexpr const char* GetTypeString(Type type) {
241 } 242 }
242} 243}
243 244
245constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) {
246 switch (image_type) {
247 case Tegra::Shader::ImageType::Texture1D:
248 return "1D";
249 case Tegra::Shader::ImageType::TextureBuffer:
250 return "Buffer";
251 case Tegra::Shader::ImageType::Texture1DArray:
252 return "1DArray";
253 case Tegra::Shader::ImageType::Texture2D:
254 return "2D";
255 case Tegra::Shader::ImageType::Texture2DArray:
256 return "2DArray";
257 case Tegra::Shader::ImageType::Texture3D:
258 return "3D";
259 default:
260 UNREACHABLE();
261 return "1D";
262 }
263}
264
244/// Generates code to use for a swizzle operation. 265/// Generates code to use for a swizzle operation.
245constexpr const char* GetSwizzle(u32 element) { 266constexpr const char* GetSwizzle(u32 element) {
246 constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; 267 constexpr std::array swizzle = {".x", ".y", ".z", ".w"};
@@ -398,8 +419,6 @@ public:
398 usage.is_read, usage.is_written); 419 usage.is_read, usage.is_written);
399 } 420 }
400 entries.clip_distances = ir.GetClipDistances(); 421 entries.clip_distances = ir.GetClipDistances();
401 entries.shader_viewport_layer_array =
402 IsVertexShader(stage) && (ir.UsesLayer() || ir.UsesViewportIndex());
403 entries.shader_length = ir.GetLength(); 422 entries.shader_length = ir.GetLength();
404 return entries; 423 return entries;
405 } 424 }
@@ -722,42 +741,6 @@ private:
722 void DeclareImages() { 741 void DeclareImages() {
723 const auto& images{ir.GetImages()}; 742 const auto& images{ir.GetImages()};
724 for (const auto& [offset, image] : images) { 743 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"; 744 std::string qualifier = "coherent volatile";
762 if (image.IsRead() && !image.IsWritten()) { 745 if (image.IsRead() && !image.IsWritten()) {
763 qualifier += " readonly"; 746 qualifier += " readonly";
@@ -765,9 +748,10 @@ private:
765 qualifier += " writeonly"; 748 qualifier += " writeonly";
766 } 749 }
767 750
768 code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " 751 const char* format = image.IsAtomic() ? "r32ui, " : "";
769 "{} {};", 752 const char* type_declaration = GetImageTypeDeclaration(image.GetType());
770 image.GetIndex(), qualifier, image_type, GetImage(image)); 753 code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format,
754 image.GetIndex(), qualifier, type_declaration, GetImage(image));
771 } 755 }
772 if (!images.empty()) { 756 if (!images.empty()) {
773 code.AddNewLine(); 757 code.AddNewLine();
@@ -1234,28 +1218,13 @@ private:
1234 } 1218 }
1235 1219
1236 std::string BuildImageValues(Operation operation) { 1220 std::string BuildImageValues(Operation operation) {
1221 constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"};
1237 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1222 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 1223
1255 const std::size_t values_count{meta.values.size()}; 1224 const std::size_t values_count{meta.values.size()};
1256 std::string expr = fmt::format("{}(", constructors.at(values_count - 1)); 1225 std::string expr = fmt::format("{}(", constructors.at(values_count - 1));
1257 for (std::size_t i = 0; i < values_count; ++i) { 1226 for (std::size_t i = 0; i < values_count; ++i) {
1258 expr += Visit(meta.values.at(i)).As(type); 1227 expr += Visit(meta.values.at(i)).AsUint();
1259 if (i + 1 < values_count) { 1228 if (i + 1 < values_count) {
1260 expr += ", "; 1229 expr += ", ";
1261 } 1230 }
@@ -1264,29 +1233,6 @@ private:
1264 return expr; 1233 return expr;
1265 } 1234 }
1266 1235
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) { 1236 Expression Assign(Operation operation) {
1291 const Node& dest = operation[0]; 1237 const Node& dest = operation[0];
1292 const Node& src = operation[1]; 1238 const Node& src = operation[1];
@@ -1545,6 +1491,8 @@ private:
1545 case Tegra::Shader::HalfType::H1_H1: 1491 case Tegra::Shader::HalfType::H1_H1:
1546 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; 1492 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
1547 } 1493 }
1494 UNREACHABLE();
1495 return {"0", Type::Int};
1548 } 1496 }
1549 1497
1550 Expression HMergeF32(Operation operation) { 1498 Expression HMergeF32(Operation operation) {
@@ -1809,6 +1757,19 @@ private:
1809 return {tmp, Type::Float}; 1757 return {tmp, Type::Float};
1810 } 1758 }
1811 1759
1760 Expression ImageLoad(Operation operation) {
1761 if (!device.HasImageLoadFormatted()) {
1762 LOG_ERROR(Render_OpenGL,
1763 "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load");
1764 return {"0", Type::Int};
1765 }
1766
1767 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1768 return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image),
1769 BuildIntegerCoordinates(operation), GetSwizzle(meta.element)),
1770 Type::Uint};
1771 }
1772
1812 Expression ImageStore(Operation operation) { 1773 Expression ImageStore(Operation operation) {
1813 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1774 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1814 code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), 1775 code.AddLine("imageStore({}, {}, {});", GetImage(meta.image),
@@ -1816,31 +1777,14 @@ private:
1816 return {}; 1777 return {};
1817 } 1778 }
1818 1779
1819 Expression AtomicImageAdd(Operation operation) { 1780 template <const std::string_view& opname>
1820 return AtomicImage(operation, "imageAtomicAdd"); 1781 Expression AtomicImage(Operation operation) {
1821 } 1782 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1822 1783 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 1784
1842 Expression AtomicImageExchange(Operation operation) { 1785 return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image),
1843 return AtomicImage(operation, "imageAtomicExchange"); 1786 BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()),
1787 Type::Uint};
1844 } 1788 }
1845 1789
1846 Expression Branch(Operation operation) { 1790 Expression Branch(Operation operation) {
@@ -2035,6 +1979,12 @@ private:
2035 Func() = delete; 1979 Func() = delete;
2036 ~Func() = delete; 1980 ~Func() = delete;
2037 1981
1982 static constexpr std::string_view Add = "Add";
1983 static constexpr std::string_view And = "And";
1984 static constexpr std::string_view Or = "Or";
1985 static constexpr std::string_view Xor = "Xor";
1986 static constexpr std::string_view Exchange = "Exchange";
1987
2038 static constexpr std::string_view ShuffleIndexed = "shuffleNV"; 1988 static constexpr std::string_view ShuffleIndexed = "shuffleNV";
2039 static constexpr std::string_view ShuffleUp = "shuffleUpNV"; 1989 static constexpr std::string_view ShuffleUp = "shuffleUpNV";
2040 static constexpr std::string_view ShuffleDown = "shuffleDownNV"; 1990 static constexpr std::string_view ShuffleDown = "shuffleDownNV";
@@ -2172,14 +2122,14 @@ private:
2172 &GLSLDecompiler::TextureQueryLod, 2122 &GLSLDecompiler::TextureQueryLod,
2173 &GLSLDecompiler::TexelFetch, 2123 &GLSLDecompiler::TexelFetch,
2174 2124
2125 &GLSLDecompiler::ImageLoad,
2175 &GLSLDecompiler::ImageStore, 2126 &GLSLDecompiler::ImageStore,
2176 &GLSLDecompiler::AtomicImageAdd, 2127
2177 &GLSLDecompiler::AtomicImageMin, 2128 &GLSLDecompiler::AtomicImage<Func::Add>,
2178 &GLSLDecompiler::AtomicImageMax, 2129 &GLSLDecompiler::AtomicImage<Func::And>,
2179 &GLSLDecompiler::AtomicImageAnd, 2130 &GLSLDecompiler::AtomicImage<Func::Or>,
2180 &GLSLDecompiler::AtomicImageOr, 2131 &GLSLDecompiler::AtomicImage<Func::Xor>,
2181 &GLSLDecompiler::AtomicImageXor, 2132 &GLSLDecompiler::AtomicImage<Func::Exchange>,
2182 &GLSLDecompiler::AtomicImageExchange,
2183 2133
2184 &GLSLDecompiler::Branch, 2134 &GLSLDecompiler::Branch,
2185 &GLSLDecompiler::BranchIndirect, 2135 &GLSLDecompiler::BranchIndirect,
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..6a7012b54 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -343,20 +343,17 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
343 u8 is_bindless{}; 343 u8 is_bindless{};
344 u8 is_written{}; 344 u8 is_written{};
345 u8 is_read{}; 345 u8 is_read{};
346 u8 is_size_known{}; 346 u8 is_atomic{};
347 u32 size{};
348 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || 347 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
349 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || 348 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
350 !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || 349 !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) ||
351 !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) { 350 !LoadObjectFromPrecompiled(is_atomic)) {
352 return {}; 351 return {};
353 } 352 }
354 entry.entries.images.emplace_back( 353 entry.entries.images.emplace_back(
355 static_cast<std::size_t>(offset), static_cast<std::size_t>(index), 354 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, 355 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0,
357 is_read != 0, 356 is_read != 0, is_atomic != 0);
358 is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size))
359 : std::nullopt);
360 } 357 }
361 358
362 u32 global_memory_count{}; 359 u32 global_memory_count{};
@@ -382,12 +379,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
382 } 379 }
383 } 380 }
384 381
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{}; 382 u64 shader_length{};
392 if (!LoadObjectFromPrecompiled(shader_length)) { 383 if (!LoadObjectFromPrecompiled(shader_length)) {
393 return {}; 384 return {};
@@ -435,14 +426,13 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
435 return false; 426 return false;
436 } 427 }
437 for (const auto& image : entries.images) { 428 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())) || 429 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
440 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || 430 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
441 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || 431 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
442 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) || 432 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
443 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) || 433 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||
444 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || 434 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
445 !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) { 435 !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) {
446 return false; 436 return false;
447 } 437 }
448 } 438 }
@@ -464,10 +454,6 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
464 } 454 }
465 } 455 }
466 456
467 if (!SaveObjectToPrecompiled(entries.shader_viewport_layer_array)) {
468 return false;
469 }
470
471 if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) { 457 if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
472 return false; 458 return false;
473 } 459 }
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index f7fbbb6e4..77fc58f25 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 {
@@ -939,22 +940,17 @@ private:
939 return {}; 940 return {};
940 } 941 }
941 942
942 Id ImageStore(Operation operation) { 943 Id ImageLoad(Operation operation) {
943 UNIMPLEMENTED();
944 return {};
945 }
946
947 Id AtomicImageAdd(Operation operation) {
948 UNIMPLEMENTED(); 944 UNIMPLEMENTED();
949 return {}; 945 return {};
950 } 946 }
951 947
952 Id AtomicImageMin(Operation operation) { 948 Id ImageStore(Operation operation) {
953 UNIMPLEMENTED(); 949 UNIMPLEMENTED();
954 return {}; 950 return {};
955 } 951 }
956 952
957 Id AtomicImageMax(Operation operation) { 953 Id AtomicImageAdd(Operation operation) {
958 UNIMPLEMENTED(); 954 UNIMPLEMENTED();
959 return {}; 955 return {};
960 } 956 }
@@ -1440,10 +1436,9 @@ private:
1440 &SPIRVDecompiler::TextureQueryLod, 1436 &SPIRVDecompiler::TextureQueryLod,
1441 &SPIRVDecompiler::TexelFetch, 1437 &SPIRVDecompiler::TexelFetch,
1442 1438
1439 &SPIRVDecompiler::ImageLoad,
1443 &SPIRVDecompiler::ImageStore, 1440 &SPIRVDecompiler::ImageStore,
1444 &SPIRVDecompiler::AtomicImageAdd, 1441 &SPIRVDecompiler::AtomicImageAdd,
1445 &SPIRVDecompiler::AtomicImageMin,
1446 &SPIRVDecompiler::AtomicImageMax,
1447 &SPIRVDecompiler::AtomicImageAnd, 1442 &SPIRVDecompiler::AtomicImageAnd,
1448 &SPIRVDecompiler::AtomicImageOr, 1443 &SPIRVDecompiler::AtomicImageOr,
1449 &SPIRVDecompiler::AtomicImageXor, 1444 &SPIRVDecompiler::AtomicImageXor,
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/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.h b/src/video_core/shader/shader_ir.h
index 2f03d83ba..6f666ee30 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -284,16 +284,13 @@ private:
284 bool is_shadow); 284 bool is_shadow);
285 285
286 /// Accesses an image. 286 /// Accesses an image.
287 Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, 287 Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
288 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
289 288
290 /// Access a bindless image sampler. 289 /// Access a bindless image sampler.
291 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, 290 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
292 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
293 291
294 /// Tries to access an existing image, updating it's state as needed 292 /// Tries to access an existing image, updating it's state as needed
295 Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, 293 Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type);
296 std::optional<Tegra::Shader::ImageAtomicSize> size);
297 294
298 /// Extracts a sequence of bits from a node 295 /// Extracts a sequence of bits from a node
299 Node BitfieldExtract(Node value, u32 offset, u32 bits); 296 Node BitfieldExtract(Node value, u32 offset, u32 bits);
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index dc6fa07fc..ff1c1d985 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -66,6 +66,9 @@ add_executable(yuzu
66 configuration/configure_profile_manager.cpp 66 configuration/configure_profile_manager.cpp
67 configuration/configure_profile_manager.h 67 configuration/configure_profile_manager.h
68 configuration/configure_profile_manager.ui 68 configuration/configure_profile_manager.ui
69 configuration/configure_service.cpp
70 configuration/configure_service.h
71 configuration/configure_service.ui
69 configuration/configure_system.cpp 72 configuration/configure_system.cpp
70 configuration/configure_system.h 73 configuration/configure_system.h
71 configuration/configure_system.ui 74 configuration/configure_system.ui
@@ -186,6 +189,10 @@ if (YUZU_USE_QT_WEB_ENGINE)
186 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) 189 target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
187endif () 190endif ()
188 191
192if (YUZU_ENABLE_BOXCAT)
193 target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT)
194endif ()
195
189if(UNIX AND NOT APPLE) 196if(UNIX AND NOT APPLE)
190 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") 197 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
191endif() 198endif()
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 92d9fb161..4cb27ddb2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -525,6 +525,17 @@ void Config::ReadDebuggingValues() {
525 qt_config->endGroup(); 525 qt_config->endGroup();
526} 526}
527 527
528void Config::ReadServiceValues() {
529 qt_config->beginGroup(QStringLiteral("Services"));
530 Settings::values.bcat_backend =
531 ReadSetting(QStringLiteral("bcat_backend"), QStringLiteral("boxcat"))
532 .toString()
533 .toStdString();
534 Settings::values.bcat_boxcat_local =
535 ReadSetting(QStringLiteral("bcat_boxcat_local"), false).toBool();
536 qt_config->endGroup();
537}
538
528void Config::ReadDisabledAddOnValues() { 539void Config::ReadDisabledAddOnValues() {
529 const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns")); 540 const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
530 541
@@ -769,6 +780,7 @@ void Config::ReadValues() {
769 ReadMiscellaneousValues(); 780 ReadMiscellaneousValues();
770 ReadDebuggingValues(); 781 ReadDebuggingValues();
771 ReadWebServiceValues(); 782 ReadWebServiceValues();
783 ReadServiceValues();
772 ReadDisabledAddOnValues(); 784 ReadDisabledAddOnValues();
773 ReadUIValues(); 785 ReadUIValues();
774} 786}
@@ -866,6 +878,7 @@ void Config::SaveValues() {
866 SaveMiscellaneousValues(); 878 SaveMiscellaneousValues();
867 SaveDebuggingValues(); 879 SaveDebuggingValues();
868 SaveWebServiceValues(); 880 SaveWebServiceValues();
881 SaveServiceValues();
869 SaveDisabledAddOnValues(); 882 SaveDisabledAddOnValues();
870 SaveUIValues(); 883 SaveUIValues();
871} 884}
@@ -963,6 +976,14 @@ void Config::SaveDebuggingValues() {
963 qt_config->endGroup(); 976 qt_config->endGroup();
964} 977}
965 978
979void Config::SaveServiceValues() {
980 qt_config->beginGroup(QStringLiteral("Services"));
981 WriteSetting(QStringLiteral("bcat_backend"),
982 QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null"));
983 WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false);
984 qt_config->endGroup();
985}
986
966void Config::SaveDisabledAddOnValues() { 987void Config::SaveDisabledAddOnValues() {
967 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns")); 988 qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
968 989
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 6b523ecdd..ba6888004 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,6 +42,7 @@ private:
42 void ReadCoreValues(); 42 void ReadCoreValues();
43 void ReadDataStorageValues(); 43 void ReadDataStorageValues();
44 void ReadDebuggingValues(); 44 void ReadDebuggingValues();
45 void ReadServiceValues();
45 void ReadDisabledAddOnValues(); 46 void ReadDisabledAddOnValues();
46 void ReadMiscellaneousValues(); 47 void ReadMiscellaneousValues();
47 void ReadPathValues(); 48 void ReadPathValues();
@@ -65,6 +66,7 @@ private:
65 void SaveCoreValues(); 66 void SaveCoreValues();
66 void SaveDataStorageValues(); 67 void SaveDataStorageValues();
67 void SaveDebuggingValues(); 68 void SaveDebuggingValues();
69 void SaveServiceValues();
68 void SaveDisabledAddOnValues(); 70 void SaveDisabledAddOnValues();
69 void SaveMiscellaneousValues(); 71 void SaveMiscellaneousValues();
70 void SavePathValues(); 72 void SavePathValues();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 49fadd0ef..372427ae2 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -98,6 +98,11 @@
98 <string>Web</string> 98 <string>Web</string>
99 </attribute> 99 </attribute>
100 </widget> 100 </widget>
101 <widget class="ConfigureService" name="serviceTab">
102 <attribute name="title">
103 <string>Services</string>
104 </attribute>
105 </widget>
101 </widget> 106 </widget>
102 </item> 107 </item>
103 </layout> 108 </layout>
@@ -178,6 +183,12 @@
178 <header>configuration/configure_hotkeys.h</header> 183 <header>configuration/configure_hotkeys.h</header>
179 <container>1</container> 184 <container>1</container>
180 </customwidget> 185 </customwidget>
186 <customwidget>
187 <class>ConfigureService</class>
188 <extends>QWidget</extends>
189 <header>configuration/configure_service.h</header>
190 <container>1</container>
191 </customwidget>
181 </customwidgets> 192 </customwidgets>
182 <resources/> 193 <resources/>
183 <connections> 194 <connections>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 7c875ae87..25b2e1b05 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -44,6 +44,7 @@ void ConfigureDialog::ApplyConfiguration() {
44 ui->audioTab->ApplyConfiguration(); 44 ui->audioTab->ApplyConfiguration();
45 ui->debugTab->ApplyConfiguration(); 45 ui->debugTab->ApplyConfiguration();
46 ui->webTab->ApplyConfiguration(); 46 ui->webTab->ApplyConfiguration();
47 ui->serviceTab->ApplyConfiguration();
47 Settings::Apply(); 48 Settings::Apply();
48 Settings::LogSettings(); 49 Settings::LogSettings();
49} 50}
@@ -74,7 +75,8 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
74void ConfigureDialog::PopulateSelectionList() { 75void ConfigureDialog::PopulateSelectionList() {
75 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ 76 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
76 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, 77 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
77 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}}, 78 {tr("System"),
79 {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}},
78 {tr("Graphics"), {ui->graphicsTab}}, 80 {tr("Graphics"), {ui->graphicsTab}},
79 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, 81 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
80 }; 82 };
@@ -108,6 +110,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
108 {ui->webTab, tr("Web")}, 110 {ui->webTab, tr("Web")},
109 {ui->gameListTab, tr("Game List")}, 111 {ui->gameListTab, tr("Game List")},
110 {ui->filesystemTab, tr("Filesystem")}, 112 {ui->filesystemTab, tr("Filesystem")},
113 {ui->serviceTab, tr("Services")},
111 }; 114 };
112 115
113 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); 116 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index b49446be9..98bc9b391 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -2,6 +2,7 @@
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 <QCheckBox>
5#include <QSpinBox> 6#include <QSpinBox>
6#include "core/core.h" 7#include "core/core.h"
7#include "core/settings.h" 8#include "core/settings.h"
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
new file mode 100644
index 000000000..81c9e933f
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -0,0 +1,136 @@
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::Offline:
77 return {QString{},
78 tr("The boxcat service is offline or you are not connected to the internet.")};
79 case Service::BCAT::Boxcat::StatusResult::ParseError:
80 return {QString{},
81 tr("There was an error while processing the boxcat event data. Contact the yuzu "
82 "developers.")};
83 case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
84 return {QString{},
85 tr("The version of yuzu you are using is either too new or too old for the server. "
86 "Try updating to the latest official release of yuzu.")};
87 }
88
89 if (map.empty()) {
90 return {QStringLiteral("Current Boxcat Events"),
91 tr("There are currently no events on boxcat.")};
92 }
93
94 QString out;
95
96 if (global.has_value()) {
97 out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global));
98 }
99
100 for (const auto& [key, value] : map) {
101 out += QStringLiteral("%1<b>%2</b><br>%3")
102 .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
103 .arg(QString::fromStdString(key))
104 .arg(FormatEventStatusString(value));
105 }
106 return {QStringLiteral("Current Boxcat Events"), std::move(out)};
107}
108
109void ConfigureService::OnBCATImplChanged() {
110#ifdef YUZU_ENABLE_BOXCAT
111 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
112 ui->bcat_empty_header->setHidden(!boxcat);
113 ui->bcat_empty_label->setHidden(!boxcat);
114 ui->bcat_empty_header->setText(QString{});
115 ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
116
117 if (!boxcat)
118 return;
119
120 const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); });
121
122 watcher.setFuture(future);
123 connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this,
124 [this] { OnUpdateBCATEmptyLabel(watcher.result()); });
125#endif
126}
127
128void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
129#ifdef YUZU_ENABLE_BOXCAT
130 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
131 if (boxcat) {
132 ui->bcat_empty_header->setText(string.first);
133 ui->bcat_empty_label->setText(string.second);
134 }
135#endif
136}
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/main.cpp b/src/yuzu/main.cpp
index f147044d9..2d82df739 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -22,6 +22,8 @@
22#include "core/frontend/applets/general_frontend.h" 22#include "core/frontend/applets/general_frontend.h"
23#include "core/frontend/scope_acquire_window_context.h" 23#include "core/frontend/scope_acquire_window_context.h"
24#include "core/hle/service/acc/profile_manager.h" 24#include "core/hle/service/acc/profile_manager.h"
25#include "core/hle/service/am/applet_ae.h"
26#include "core/hle/service/am/applet_oe.h"
25#include "core/hle/service/am/applets/applets.h" 27#include "core/hle/service/am/applets/applets.h"
26#include "core/hle/service/hid/controllers/npad.h" 28#include "core/hle/service/hid/controllers/npad.h"
27#include "core/hle/service/hid/hid.h" 29#include "core/hle/service/hid/hid.h"
@@ -83,6 +85,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
83#include "core/file_sys/submission_package.h" 85#include "core/file_sys/submission_package.h"
84#include "core/frontend/applets/software_keyboard.h" 86#include "core/frontend/applets/software_keyboard.h"
85#include "core/hle/kernel/process.h" 87#include "core/hle/kernel/process.h"
88#include "core/hle/service/am/am.h"
86#include "core/hle/service/filesystem/filesystem.h" 89#include "core/hle/service/filesystem/filesystem.h"
87#include "core/hle/service/nfp/nfp.h" 90#include "core/hle/service/nfp/nfp.h"
88#include "core/hle/service/sm/sm.h" 91#include "core/hle/service/sm/sm.h"
@@ -1674,6 +1677,11 @@ void GMainWindow::OnStartGame() {
1674} 1677}
1675 1678
1676void GMainWindow::OnPauseGame() { 1679void GMainWindow::OnPauseGame() {
1680 Core::System& system{Core::System::GetInstance()};
1681 if (system.GetExitLock() && !ConfirmForceLockedExit()) {
1682 return;
1683 }
1684
1677 emu_thread->SetRunning(false); 1685 emu_thread->SetRunning(false);
1678 1686
1679 ui.action_Start->setEnabled(true); 1687 ui.action_Start->setEnabled(true);
@@ -1685,6 +1693,11 @@ void GMainWindow::OnPauseGame() {
1685} 1693}
1686 1694
1687void GMainWindow::OnStopGame() { 1695void GMainWindow::OnStopGame() {
1696 Core::System& system{Core::System::GetInstance()};
1697 if (system.GetExitLock() && !ConfirmForceLockedExit()) {
1698 return;
1699 }
1700
1688 ShutdownGame(); 1701 ShutdownGame();
1689} 1702}
1690 1703
@@ -2182,13 +2195,41 @@ bool GMainWindow::ConfirmChangeGame() {
2182 if (emu_thread == nullptr) 2195 if (emu_thread == nullptr)
2183 return true; 2196 return true;
2184 2197
2185 auto answer = QMessageBox::question( 2198 const auto answer = QMessageBox::question(
2186 this, tr("yuzu"), 2199 this, tr("yuzu"),
2187 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), 2200 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
2188 QMessageBox::Yes | QMessageBox::No, QMessageBox::No); 2201 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
2189 return answer != QMessageBox::No; 2202 return answer != QMessageBox::No;
2190} 2203}
2191 2204
2205bool GMainWindow::ConfirmForceLockedExit() {
2206 if (emu_thread == nullptr)
2207 return true;
2208
2209 const auto answer =
2210 QMessageBox::question(this, tr("yuzu"),
2211 tr("The currently running application has requested yuzu to not "
2212 "exit.\n\nWould you like to bypass this and exit anyway?"),
2213 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
2214 return answer != QMessageBox::No;
2215}
2216
2217void GMainWindow::RequestGameExit() {
2218 auto& sm{Core::System::GetInstance().ServiceManager()};
2219 auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
2220 auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
2221 bool has_signalled = false;
2222
2223 if (applet_oe != nullptr) {
2224 applet_oe->GetMessageQueue()->RequestExit();
2225 has_signalled = true;
2226 }
2227
2228 if (applet_ae != nullptr && !has_signalled) {
2229 applet_ae->GetMessageQueue()->RequestExit();
2230 }
2231}
2232
2192void GMainWindow::filterBarSetChecked(bool state) { 2233void GMainWindow::filterBarSetChecked(bool state) {
2193 ui.action_Show_Filter_Bar->setChecked(state); 2234 ui.action_Show_Filter_Bar->setChecked(state);
2194 emit(OnToggleFilterBar()); 2235 emit(OnToggleFilterBar());
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7d16188cb..e942d1248 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -172,6 +172,8 @@ private:
172 */ 172 */
173 bool ConfirmClose(); 173 bool ConfirmClose();
174 bool ConfirmChangeGame(); 174 bool ConfirmChangeGame();
175 bool ConfirmForceLockedExit();
176 void RequestGameExit();
175 void closeEvent(QCloseEvent* event) override; 177 void closeEvent(QCloseEvent* event) override;
176 178
177private slots: 179private slots:
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 '|'):