summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt21
-rw-r--r--src/common/alignment.h10
-rw-r--r--src/common/file_util.cpp3
-rw-r--r--src/common/scm_rev.cpp.in6
-rw-r--r--src/common/scm_rev.h3
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/core.cpp12
-rw-r--r--src/core/core.h11
-rw-r--r--src/core/crypto/key_manager.cpp2
-rw-r--r--src/core/file_sys/bis_factory.cpp5
-rw-r--r--src/core/file_sys/bis_factory.h2
-rw-r--r--src/core/file_sys/romfs_factory.cpp4
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp5
-rw-r--r--src/core/file_sys/vfs_libzip.cpp79
-rw-r--r--src/core/file_sys/vfs_libzip.h13
-rw-r--r--src/core/gdbstub/gdbstub.cpp3
-rw-r--r--src/core/hle/kernel/handle_table.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp69
-rw-r--r--src/core/hle/service/am/am.h5
-rw-r--r--src/core/hle/service/am/applets/applets.cpp4
-rw-r--r--src/core/hle/service/am/applets/applets.h2
-rw-r--r--src/core/hle/service/apm/controller.cpp3
-rw-r--r--src/core/hle/service/apm/controller.h6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp137
-rw-r--r--src/core/hle/service/bcat/backend/backend.h150
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp504
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h58
-rw-r--r--src/core/hle/service/bcat/bcat.cpp9
-rw-r--r--src/core/hle/service/bcat/bcat.h7
-rw-r--r--src/core/hle/service/bcat/module.cpp561
-rw-r--r--src/core/hle/service/bcat/module.h31
-rw-r--r--src/core/hle/service/fatal/fatal.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp29
-rw-r--r--src/core/hle/service/filesystem/filesystem.h10
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp2
-rw-r--r--src/core/hle/service/friend/friend.cpp1
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp5
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h1
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h1
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h1
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h1
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp18
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h1
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp19
-rw-r--r--src/core/hle/service/hid/hid.h1
-rw-r--r--src/core/hle/service/ldr/ldr.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp.cpp9
-rw-r--r--src/core/hle/service/nifm/nifm.cpp13
-rw-r--r--src/core/hle/service/ns/pl_u.cpp8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp2
-rw-r--r--src/core/hle/service/service.cpp2
-rw-r--r--src/core/loader/nso.cpp1
-rw-r--r--src/core/memory.cpp10
-rw-r--r--src/core/settings.cpp2
-rw-r--r--src/core/settings.h4
-rw-r--r--src/video_core/CMakeLists.txt6
-rw-r--r--src/video_core/engines/shader_bytecode.h8
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_device.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp446
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp50
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp374
-rw-r--r--src/video_core/shader/ast.cpp738
-rw-r--r--src/video_core/shader/ast.h400
-rw-r--r--src/video_core/shader/compiler_settings.cpp26
-rw-r--r--src/video_core/shader/compiler_settings.h26
-rw-r--r--src/video_core/shader/control_flow.cpp155
-rw-r--r--src/video_core/shader/control_flow.h14
-rw-r--r--src/video_core/shader/decode.cpp170
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp5
-rw-r--r--src/video_core/shader/decode/image.cpp137
-rw-r--r--src/video_core/shader/expr.cpp93
-rw-r--r--src/video_core/shader/expr.h139
-rw-r--r--src/video_core/shader/node.h46
-rw-r--r--src/video_core/shader/shader_ir.cpp12
-rw-r--r--src/video_core/shader/shader_ir.h42
-rw-r--r--src/video_core/texture_cache/texture_cache.h146
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp21
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_service.cpp138
-rw-r--r--src/yuzu/configuration/configure_service.h34
-rw-r--r--src/yuzu/configuration/configure_service.ui124
-rw-r--r--src/yuzu/game_list.cpp8
-rw-r--r--src/yuzu/game_list_p.h9
-rw-r--r--src/yuzu/game_list_worker.cpp12
-rw-r--r--src/yuzu/game_list_worker.h5
-rw-r--r--src/yuzu/main.cpp21
-rw-r--r--src/yuzu/uisettings.cpp2
-rw-r--r--src/yuzu/uisettings.h2
-rw-r--r--src/yuzu_cmd/config.cpp5
-rw-r--r--src/yuzu_cmd/default_ini.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp13
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h3
-rw-r--r--src/yuzu_cmd/yuzu.cpp4
-rw-r--r--src/yuzu_tester/config.cpp1
115 files changed, 4885 insertions, 575 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 01abdb3bb..906c486fd 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -10,13 +10,28 @@ if (DEFINED ENV{CI})
elseif(DEFINED ENV{APPVEYOR})
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
+ elseif(DEFINED ENV{AZURE})
+ set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME})
+ set(BUILD_TAG $ENV{AZURE_REPO_TAG})
endif()
endif()
+if (DEFINED ENV{TITLEBARFORMATIDLE})
+ set(TITLE_BAR_FORMAT_IDLE $ENV{TITLEBARFORMATIDLE})
+endif ()
+if (DEFINED ENV{TITLEBARFORMATRUNNING})
+ set(TITLE_BAR_FORMAT_RUNNING $ENV{TITLEBARFORMATRUNNING})
+endif ()
+if (DEFINED ENV{DISPLAYVERSION})
+ set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
+endif ()
add_custom_command(OUTPUT scm_rev.cpp
COMMAND ${CMAKE_COMMAND}
-DSRC_DIR="${CMAKE_SOURCE_DIR}"
-DBUILD_REPOSITORY="${BUILD_REPOSITORY}"
+ -DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}"
+ -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}"
-DBUILD_TAG="${BUILD_TAG}"
+ -DBUILD_ID="${DISPLAY_VERSION}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
DEPENDS
# WARNING! It was too much work to try and make a common location for this list,
@@ -57,9 +72,15 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/video.cpp"
"${VIDEO_CORE}/shader/decode/warp.cpp"
"${VIDEO_CORE}/shader/decode/xmad.cpp"
+ "${VIDEO_CORE}/shader/ast.cpp"
+ "${VIDEO_CORE}/shader/ast.h"
"${VIDEO_CORE}/shader/control_flow.cpp"
"${VIDEO_CORE}/shader/control_flow.h"
+ "${VIDEO_CORE}/shader/compiler_settings.cpp"
+ "${VIDEO_CORE}/shader/compiler_settings.h"
"${VIDEO_CORE}/shader/decode.cpp"
+ "${VIDEO_CORE}/shader/expr.cpp"
+ "${VIDEO_CORE}/shader/expr.h"
"${VIDEO_CORE}/shader/node.h"
"${VIDEO_CORE}/shader/node_helper.cpp"
"${VIDEO_CORE}/shader/node_helper.h"
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 88d5d3a65..cdd4833f8 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -51,7 +51,17 @@ public:
using reference = T&;
using const_reference = const T&;
+ using propagate_on_container_copy_assignment = std::true_type;
+ using propagate_on_container_move_assignment = std::true_type;
+ using propagate_on_container_swap = std::true_type;
+ using is_always_equal = std::true_type;
+
public:
+ constexpr AlignmentAllocator() noexcept = default;
+
+ template <typename T2>
+ constexpr AlignmentAllocator(const AlignmentAllocator<T2, Align>&) noexcept {}
+
pointer address(reference r) noexcept {
return std::addressof(r);
}
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 2d9374783..41167f57a 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -713,7 +713,6 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
case UserPath::RootDir:
user_path = paths[UserPath::RootDir] + DIR_SEP;
break;
-
case UserPath::UserDir:
user_path = paths[UserPath::RootDir] + DIR_SEP;
paths[UserPath::ConfigDir] = user_path + CONFIG_DIR DIR_SEP;
@@ -721,6 +720,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
paths[UserPath::SDMCDir] = user_path + SDMC_DIR DIR_SEP;
paths[UserPath::NANDDir] = user_path + NAND_DIR DIR_SEP;
break;
+ default:
+ break;
}
}
diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in
index d69038f65..5f126f324 100644
--- a/src/common/scm_rev.cpp.in
+++ b/src/common/scm_rev.cpp.in
@@ -11,6 +11,9 @@
#define BUILD_DATE "@BUILD_DATE@"
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
#define BUILD_VERSION "@BUILD_VERSION@"
+#define BUILD_ID "@BUILD_ID@"
+#define TITLE_BAR_FORMAT_IDLE "@TITLE_BAR_FORMAT_IDLE@"
+#define TITLE_BAR_FORMAT_RUNNING "@TITLE_BAR_FORMAT_RUNNING@"
#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@"
namespace Common {
@@ -22,6 +25,9 @@ const char g_build_name[] = BUILD_NAME;
const char g_build_date[] = BUILD_DATE;
const char g_build_fullname[] = BUILD_FULLNAME;
const char g_build_version[] = BUILD_VERSION;
+const char g_build_id[] = BUILD_ID;
+const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
+const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
const char g_shader_cache_version[] = SHADER_CACHE_VERSION;
} // namespace
diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h
index 666bf0367..563015ec9 100644
--- a/src/common/scm_rev.h
+++ b/src/common/scm_rev.h
@@ -13,6 +13,9 @@ extern const char g_build_name[];
extern const char g_build_date[];
extern const char g_build_fullname[];
extern const char g_build_version[];
+extern const char g_build_id[];
+extern const char g_title_bar_format_idle[];
+extern const char g_title_bar_format_running[];
extern const char g_shader_cache_version[];
} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index be3ffb779..3b1d72cf9 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,3 +1,9 @@
+if (YUZU_ENABLE_BOXCAT)
+ set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
+else()
+ set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
+endif()
+
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
@@ -82,6 +88,8 @@ add_library(core STATIC
file_sys/vfs_concat.h
file_sys/vfs_layered.cpp
file_sys/vfs_layered.h
+ file_sys/vfs_libzip.cpp
+ file_sys/vfs_libzip.h
file_sys/vfs_offset.cpp
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
@@ -241,6 +249,9 @@ add_library(core STATIC
hle/service/audio/errors.h
hle/service/audio/hwopus.cpp
hle/service/audio/hwopus.h
+ hle/service/bcat/backend/backend.cpp
+ hle/service/bcat/backend/backend.h
+ ${BCAT_BOXCAT_ADDITIONAL_SOURCES}
hle/service/bcat/bcat.cpp
hle/service/bcat/bcat.h
hle/service/bcat/module.cpp
@@ -501,6 +512,15 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
+
+if (YUZU_ENABLE_BOXCAT)
+ get_directory_property(OPENSSL_LIBS
+ DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
+ DEFINITION OPENSSL_LIBS)
+ target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT)
+ target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip)
+endif()
+
if (ENABLE_WEB_SERVICE)
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
diff --git a/src/core/core.cpp b/src/core/core.cpp
index bf62085a8..4d0ac72a5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -112,7 +112,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
}
struct System::Impl {
explicit Impl(System& system)
- : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {}
+ : kernel{system}, fs_controller{system}, cpu_core_manager{system},
+ applet_manager{system}, reporter{system} {}
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
@@ -343,6 +344,7 @@ struct System::Impl {
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
+ std::array<u8, 0x20> build_id{};
/// Frontend applets
Service::AM::Applets::AppletManager applet_manager;
@@ -651,6 +653,14 @@ bool System::GetExitLock() const {
return impl->exit_lock;
}
+void System::SetCurrentProcessBuildID(const CurrentBuildProcessID& id) {
+ impl->build_id = id;
+}
+
+const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const {
+ return impl->build_id;
+}
+
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
return impl->Init(*this, emu_window);
}
diff --git a/src/core/core.h b/src/core/core.h
index 0170e0b05..90e7ac607 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -8,7 +8,6 @@
#include <memory>
#include <string>
-#include <map>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
@@ -102,6 +101,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
class System {
public:
+ using CurrentBuildProcessID = std::array<u8, 0x20>;
+
System(const System&) = delete;
System& operator=(const System&) = delete;
@@ -338,6 +339,10 @@ public:
bool GetExitLock() const;
+ void SetCurrentProcessBuildID(const CurrentBuildProcessID& id);
+
+ const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
+
private:
System();
@@ -361,8 +366,4 @@ private:
static System s_instance;
};
-inline Kernel::Process* CurrentProcess() {
- return System::GetInstance().CurrentProcess();
-}
-
} // namespace Core
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 46aceec3d..222fc95ba 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -423,7 +423,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
const RSAKeyPair<2048>& key) {
const auto issuer = ticket.GetData().issuer;
- if (issuer == std::array<u8, 0x40>{})
+ if (IsAllZeroArray(issuer))
return {};
if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 8f758d6d9..0af44f340 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -136,4 +136,9 @@ u64 BISFactory::GetFullNANDTotalSpace() const {
return static_cast<u64>(Settings::values.nand_total_size);
}
+VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const {
+ return GetOrCreateDirectoryRelative(nand_root,
+ fmt::format("/system/save/bcat/{:016X}", title_id));
+}
+
} // 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:
u64 GetUserNANDTotalSpace() const;
u64 GetFullNANDTotalSpace() const;
+ VirtualDir GetBCATDirectory(u64 title_id) const;
+
private:
VirtualDir nand_root;
VirtualDir load_root;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 84cd4684c..4bd2e6183 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -35,11 +35,11 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
this->update_raw = std::move(update_raw);
}
-ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const {
+ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
if (!updatable)
return MakeResult<VirtualFile>(file);
- const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
+ const PatchManager patch_manager(current_process_title_id);
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index da63a313a..c5d40285c 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -33,7 +33,7 @@ public:
~RomFSFactory();
void SetPackedUpdate(VirtualFile update_raw);
- ResultVal<VirtualFile> OpenCurrentProcess() const;
+ ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
private:
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index f77cc02ac..fc8755c78 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -127,8 +127,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
- if (type == SaveDataType::SaveData && title_id == 0)
- title_id = Core::CurrentProcess()->GetTitleID();
+ if (type == SaveDataType::SaveData && title_id == 0) {
+ title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ }
std::string out = GetSaveDataSpaceIdPath(space);
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 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+#include <zip.h>
+#include "common/logging/backend.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_libzip.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys {
+
+VirtualDir ExtractZIP(VirtualFile file) {
+ zip_error_t error{};
+
+ const auto data = file->ReadAllBytes();
+ std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{
+ zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close};
+ if (src == nullptr)
+ return nullptr;
+
+ std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error),
+ zip_close};
+ if (zip == nullptr)
+ return nullptr;
+
+ std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>();
+
+ const auto num_entries = zip_get_num_entries(zip.get(), 0);
+
+ zip_stat_t stat{};
+ zip_stat_init(&stat);
+
+ for (std::size_t i = 0; i < num_entries; ++i) {
+ const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat);
+ if (stat_res == -1)
+ return nullptr;
+
+ const std::string name(stat.name);
+ if (name.empty())
+ continue;
+
+ if (name.back() != '/') {
+ std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file{
+ zip_fopen_index(zip.get(), i, 0), zip_fclose};
+
+ std::vector<u8> buf(stat.size);
+ if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size())
+ return nullptr;
+
+ const auto parts = FileUtil::SplitPathComponents(stat.name);
+ const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
+
+ std::shared_ptr<VectorVfsDirectory> dtrv = out;
+ for (std::size_t j = 0; j < parts.size() - 1; ++j) {
+ if (dtrv == nullptr)
+ return nullptr;
+ const auto subdir = dtrv->GetSubdirectory(parts[j]);
+ if (subdir == nullptr) {
+ const auto temp = std::make_shared<VectorVfsDirectory>(
+ std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parts[j]);
+ dtrv->AddDirectory(temp);
+ dtrv = temp;
+ } else {
+ dtrv = std::dynamic_pointer_cast<VectorVfsDirectory>(subdir);
+ }
+ }
+
+ if (dtrv == nullptr)
+ return nullptr;
+ dtrv->AddFile(new_file);
+ }
+ }
+
+ return out;
+}
+
+} // 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 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys {
+
+VirtualDir ExtractZIP(VirtualFile zip);
+
+} // namespace FileSys
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index afa812598..db51d722f 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -641,7 +641,8 @@ static void HandleQuery() {
strlen("Xfer:features:read:target.xml:")) == 0) {
SendReply(target_xml);
} else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
- const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
+ const VAddr base_address =
+ Core::System::GetInstance().CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
std::string buffer = fmt::format("TextSeg={:0x}", base_address);
SendReply(buffer.c_str());
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index bdfaa977f..2cc5d536b 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -103,7 +103,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return GetCurrentThread();
} else if (handle == CurrentProcess) {
- return Core::CurrentProcess();
+ return Core::System::GetInstance().CurrentProcess();
}
if (!IsValid(handle)) {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 797c9a06f..941ebc93a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -31,6 +31,7 @@
#include "core/hle/service/am/tcap.h"
#include "core/hle/service/apm/controller.h"
#include "core/hle/service/apm/interface.h"
+#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -46,15 +47,20 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2};
constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3};
constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
-constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
+enum class LaunchParameterKind : u32 {
+ ApplicationSpecific = 1,
+ AccountPreselectedUser = 2,
+};
+
+constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA;
-struct LaunchParameters {
+struct LaunchParameterAccountPreselectedUser {
u32_le magic;
u32_le is_account_selected;
u128 current_user;
INSERT_PADDING_BYTES(0x70);
};
-static_assert(sizeof(LaunchParameters) == 0x88);
+static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88);
IWindowController::IWindowController(Core::System& system_)
: ServiceFramework("IWindowController"), system{system_} {
@@ -1128,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
}
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_AM, "called");
+ IPC::RequestParser rp{ctx};
+ const auto kind = rp.PopEnum<LaunchParameterKind>();
- LaunchParameters params{};
+ LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
- params.magic = POP_LAUNCH_PARAMETER_MAGIC;
- params.is_account_selected = 1;
+ if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
+ const auto backend = BCAT::CreateBackendFromSettings(
+ [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
+ const auto build_id_full = system.GetCurrentProcessBuildID();
+ u64 build_id{};
+ std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
- Account::ProfileManager profile_manager{};
- const auto uuid = profile_manager.GetUser(Settings::values.current_user);
- ASSERT(uuid);
- params.current_user = uuid->uuid;
+ const auto data =
+ backend->GetLaunchParameter({system.CurrentProcess()->GetTitleID(), build_id});
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ if (data.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<AM::IStorage>(*data);
+ launch_popped_application_specific = true;
+ return;
+ }
+ } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
+ !launch_popped_account_preselect) {
+ LaunchParameterAccountPreselectedUser params{};
- rb.Push(RESULT_SUCCESS);
+ params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
+ params.is_account_selected = 1;
- std::vector<u8> buffer(sizeof(LaunchParameters));
- std::memcpy(buffer.data(), &params, buffer.size());
+ Account::ProfileManager profile_manager{};
+ const auto uuid = profile_manager.GetUser(Settings::values.current_user);
+ ASSERT(uuid);
+ params.current_user = uuid->uuid;
- rb.PushIpcInterface<AM::IStorage>(buffer);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(RESULT_SUCCESS);
+
+ std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
+ std::memcpy(buffer.data(), &params, buffer.size());
+
+ rb.PushIpcInterface<AM::IStorage>(buffer);
+ launch_popped_account_preselect = true;
+ return;
+ }
+
+ LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NO_DATA_IN_CHANNEL);
}
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
@@ -1165,7 +1200,7 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
FileSys::SaveDataDescriptor descriptor{};
- descriptor.title_id = Core::CurrentProcess()->GetTitleID();
+ descriptor.title_id = system.CurrentProcess()->GetTitleID();
descriptor.user_id = user_id;
descriptor.type = FileSys::SaveDataType::SaveData;
const auto res = system.GetFileSystemController().CreateSaveData(
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index a3baeb673..ccd053c13 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -147,6 +147,7 @@ private:
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
+ Core::System& system;
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
Kernel::EventPair launchable_event;
Kernel::EventPair accumulated_suspended_tick_changed_event;
@@ -154,8 +155,6 @@ private:
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
bool is_auto_sleep_disabled = false;
-
- Core::System& system;
};
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
@@ -255,6 +254,8 @@ private:
void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
+ bool launch_popped_application_specific = false;
+ bool launch_popped_account_preselect = false;
Kernel::EventPair gpu_error_detected_event;
Core::System& system;
};
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_} {}
AppletManager::~AppletManager() = default;
+const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
+ return frontend;
+}
+
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.parental_controls != nullptr)
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:
explicit AppletManager(Core::System& system_);
~AppletManager();
+ const AppletFrontendSet& GetAppletFrontendSet() const;
+
void SetAppletFrontendSet(AppletFrontendSet set);
void SetDefaultAppletFrontendSet();
void SetDefaultAppletsIfMissing();
diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp
index 4376612eb..073d0f6fa 100644
--- a/src/core/hle/service/apm/controller.cpp
+++ b/src/core/hle/service/apm/controller.cpp
@@ -13,7 +13,7 @@ constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION =
PerformanceConfiguration::Config7;
Controller::Controller(Core::Timing::CoreTiming& core_timing)
- : core_timing(core_timing), configs{
+ : core_timing{core_timing}, configs{
{PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION},
{PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION},
} {}
@@ -63,6 +63,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa
void Controller::SetClockSpeed(u32 mhz) {
LOG_INFO(Service_APM, "called, mhz={:08X}", mhz);
// TODO(DarkLordZach): Actually signal core_timing to change clock speed.
+ // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used.
}
} // namespace Service::APM
diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h
index 8ac80eaea..454caa6eb 100644
--- a/src/core/hle/service/apm/controller.h
+++ b/src/core/hle/service/apm/controller.h
@@ -50,7 +50,7 @@ enum class PerformanceMode : u8 {
// system during times of high load -- this simply maps to different PerformanceConfigs to use.
class Controller {
public:
- Controller(Core::Timing::CoreTiming& core_timing);
+ explicit Controller(Core::Timing::CoreTiming& core_timing);
~Controller();
void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config);
@@ -62,9 +62,9 @@ public:
private:
void SetClockSpeed(u32 mhz);
- std::map<PerformanceMode, PerformanceConfiguration> configs;
+ [[maybe_unused]] Core::Timing::CoreTiming& core_timing;
- Core::Timing::CoreTiming& core_timing;
+ std::map<PerformanceMode, PerformanceConfiguration> configs;
};
} // namespace Service::APM
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index fb84a8f13..9afefb5c6 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -205,7 +205,7 @@ private:
AudioCore::StreamPtr stream;
std::string device_name;
- AudoutParams audio_params{};
+ [[maybe_unused]] AudoutParams audio_params {};
/// This is the event handle used to check if the audio buffer was released
Kernel::EventPair buffer_event;
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
new file mode 100644
index 000000000..9d6946bc5
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -0,0 +1,137 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/lock.h"
+#include "core/hle/service/bcat/backend/backend.h"
+
+namespace Service::BCAT {
+
+ProgressServiceBackend::ProgressServiceBackend(std::string_view event_name) {
+ auto& kernel{Core::System::GetInstance().Kernel()};
+ event = Kernel::WritableEvent::CreateEventPair(
+ kernel, Kernel::ResetType::Automatic,
+ std::string("ProgressServiceBackend:UpdateEvent:").append(event_name));
+}
+
+Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const {
+ return event.readable;
+}
+
+DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
+ return impl;
+}
+
+void ProgressServiceBackend::SetNeedHLELock(bool need) {
+ need_hle_lock = need;
+}
+
+void ProgressServiceBackend::SetTotalSize(u64 size) {
+ impl.total_bytes = size;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartConnecting() {
+ impl.status = DeliveryCacheProgressImpl::Status::Connecting;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartProcessingDataList() {
+ impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
+ std::string_view file_name, u64 file_size) {
+ impl.status = DeliveryCacheProgressImpl::Status::Downloading;
+ impl.current_downloaded_bytes = 0;
+ impl.current_total_bytes = file_size;
+ std::memcpy(impl.current_directory.data(), dir_name.data(),
+ std::min<u64>(dir_name.size(), 0x31ull));
+ std::memcpy(impl.current_file.data(), file_name.data(),
+ std::min<u64>(file_name.size(), 0x31ull));
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
+ impl.current_downloaded_bytes = downloaded;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::FinishDownloadingFile() {
+ impl.total_downloaded_bytes += impl.current_total_bytes;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
+ impl.status = DeliveryCacheProgressImpl::Status::Committing;
+ impl.current_file.fill(0);
+ impl.current_downloaded_bytes = 0;
+ impl.current_total_bytes = 0;
+ std::memcpy(impl.current_directory.data(), dir_name.data(),
+ std::min<u64>(dir_name.size(), 0x31ull));
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::FinishDownload(ResultCode result) {
+ impl.total_downloaded_bytes = impl.total_bytes;
+ impl.status = DeliveryCacheProgressImpl::Status::Done;
+ impl.result = result;
+ SignalUpdate();
+}
+
+void ProgressServiceBackend::SignalUpdate() const {
+ if (need_hle_lock) {
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ event.writable->Signal();
+ } else {
+ event.writable->Signal();
+ }
+}
+
+Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
+
+Backend::~Backend() = default;
+
+NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {}
+
+NullBackend::~NullBackend() = default;
+
+bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
+ title.build_id);
+
+ progress.FinishDownload(RESULT_SUCCESS);
+ return true;
+}
+
+bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
+ title.build_id, name);
+
+ progress.FinishDownload(RESULT_SUCCESS);
+ return true;
+}
+
+bool NullBackend::Clear(u64 title_id) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
+
+ return true;
+}
+
+void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
+ Common::HexToString(passphrase));
+}
+
+std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
+ title.build_id);
+ return std::nullopt;
+}
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
new file mode 100644
index 000000000..51dbd3316
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -0,0 +1,150 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/result.h"
+
+namespace Service::BCAT {
+
+struct DeliveryCacheProgressImpl;
+
+using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
+using Passphrase = std::array<u8, 0x20>;
+
+struct TitleIDVersion {
+ u64 title_id;
+ u64 build_id;
+};
+
+using DirectoryName = std::array<char, 0x20>;
+using FileName = std::array<char, 0x20>;
+
+struct DeliveryCacheProgressImpl {
+ enum class Status : s32 {
+ None = 0x0,
+ Queued = 0x1,
+ Connecting = 0x2,
+ ProcessingDataList = 0x3,
+ Downloading = 0x4,
+ Committing = 0x5,
+ Done = 0x9,
+ };
+
+ Status status;
+ ResultCode result = RESULT_SUCCESS;
+ DirectoryName current_directory;
+ FileName current_file;
+ s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
+ s64 current_total_bytes; ///< Bytes total on current file.
+ s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
+ s64 total_bytes; ///< Bytes total on overall download.
+ INSERT_PADDING_BYTES(
+ 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
+};
+static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
+ "DeliveryCacheProgressImpl has incorrect size.");
+
+// A class to manage the signalling to the game about BCAT download progress.
+// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
+class ProgressServiceBackend {
+ friend class IBcatService;
+
+public:
+ // Clients should call this with true if any of the functions are going to be called from a
+ // non-HLE thread and this class need to lock the hle mutex. (default is false)
+ void SetNeedHLELock(bool need);
+
+ // Sets the number of bytes total in the entire download.
+ void SetTotalSize(u64 size);
+
+ // Notifies the application that the backend has started connecting to the server.
+ void StartConnecting();
+ // Notifies the application that the backend has begun accumulating and processing metadata.
+ void StartProcessingDataList();
+
+ // Notifies the application that a file is starting to be downloaded.
+ void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
+ // Updates the progress of the current file to the size passed.
+ void UpdateFileProgress(u64 downloaded);
+ // Notifies the application that the current file has completed download.
+ void FinishDownloadingFile();
+
+ // Notifies the application that all files in this directory have completed and are being
+ // finalized.
+ void CommitDirectory(std::string_view dir_name);
+
+ // Notifies the application that the operation completed with result code result.
+ void FinishDownload(ResultCode result);
+
+private:
+ explicit ProgressServiceBackend(std::string_view event_name);
+
+ Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent() const;
+ DeliveryCacheProgressImpl& GetImpl();
+
+ void SignalUpdate() const;
+
+ DeliveryCacheProgressImpl impl{};
+ Kernel::EventPair event;
+ bool need_hle_lock = false;
+};
+
+// A class representing an abstract backend for BCAT functionality.
+class Backend {
+public:
+ explicit Backend(DirectoryGetter getter);
+ virtual ~Backend();
+
+ // Called when the backend is needed to synchronize the data for the game with title ID and
+ // version in title. A ProgressServiceBackend object is provided to alert the application of
+ // status.
+ virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
+ // Very similar to Synchronize, but only for the directory provided. Backends should not alter
+ // the data for any other directories.
+ virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) = 0;
+
+ // Removes all cached data associated with title id provided.
+ virtual bool Clear(u64 title_id) = 0;
+
+ // Sets the BCAT Passphrase to be used with the associated title ID.
+ virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
+
+ // Gets the launch parameter used by AM associated with the title ID and version provided.
+ virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
+
+protected:
+ DirectoryGetter dir_getter;
+};
+
+// A backend of BCAT that provides no operation.
+class NullBackend : public Backend {
+public:
+ explicit NullBackend(DirectoryGetter getter);
+ ~NullBackend() override;
+
+ bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
+ bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) override;
+
+ bool Clear(u64 title_id) override;
+
+ void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
+
+ std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
+};
+
+std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
new file mode 100644
index 000000000..64022982b
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -0,0 +1,504 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/ostream.h>
+#include <httplib.h>
+#include <json.hpp>
+#include <mbedtls/sha256.h>
+#include "common/hex_util.h"
+#include "common/logging/backend.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_libzip.h"
+#include "core/file_sys/vfs_vector.h"
+#include "core/frontend/applets/error.h"
+#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/bcat/backend/boxcat.h"
+#include "core/settings.h"
+
+namespace {
+
+// Prevents conflicts with windows macro called CreateFile
+FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
+ return dir->CreateFile(name);
+}
+
+// Prevents conflicts with windows macro called DeleteFile
+bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
+ return dir->DeleteFile(name);
+}
+
+} // Anonymous namespace
+
+namespace Service::BCAT {
+
+constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
+
+constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
+
+// Formatted using fmt with arg[0] = hex title id
+constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
+constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
+
+constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
+
+constexpr char BOXCAT_API_VERSION[] = "1";
+constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
+
+// HTTP status codes for Boxcat
+enum class ResponseStatus {
+ Ok = 200, ///< Operation completed successfully.
+ BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
+ NoUpdate = 304, ///< The digest provided would match the new data, no need to update.
+ NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation.
+ NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format
+ ///< issues or whatnot) and has no data.
+};
+
+enum class DownloadResult {
+ Success = 0,
+ NoResponse,
+ GeneralWebError,
+ NoMatchTitleId,
+ NoMatchBuildId,
+ InvalidContentType,
+ GeneralFSError,
+ BadClientVersion,
+};
+
+constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
+ "Success",
+ "There was no response from the server.",
+ "There was a general web error code returned from the server.",
+ "The title ID of the current game doesn't have a boxcat implementation. If you believe an "
+ "implementation should be added, contact yuzu support.",
+ "The build ID of the current version of the game is marked as incompatible with the current "
+ "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
+ "The content type of the web response was invalid.",
+ "There was a general filesystem error while saving the zip file.",
+ "The server is either too new or too old to serve the request. Try using the latest version of "
+ "an official release of yuzu.",
+};
+
+std::ostream& operator<<(std::ostream& os, DownloadResult result) {
+ return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
+}
+
+constexpr u32 PORT = 443;
+constexpr u32 TIMEOUT_SECONDS = 30;
+[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB
+
+namespace {
+
+std::string GetBINFilePath(u64 title_id) {
+ return fmt::format("{}bcat/{:016X}/launchparam.bin",
+ FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+}
+
+std::string GetZIPFilePath(u64 title_id) {
+ return fmt::format("{}bcat/{:016X}/data.zip",
+ FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
+}
+
+// If the error is something the user should know about (build ID mismatch, bad client version),
+// display an error.
+void HandleDownloadDisplayResult(DownloadResult res) {
+ if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
+ res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
+ res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
+ return;
+ }
+
+ const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()};
+ frontend.error->ShowCustomErrorText(
+ ResultCode(-1), "There was an error while attempting to use Boxcat.",
+ DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
+}
+
+bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
+ std::string_view dir_name, ProgressServiceBackend& progress,
+ std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+ if (!dest->Resize(src->GetSize()))
+ return false;
+
+ progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
+
+ std::vector<u8> temp(std::min(block_size, src->GetSize()));
+ for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
+ const auto read = std::min(block_size, src->GetSize() - i);
+
+ if (src->Read(temp.data(), read, i) != read) {
+ return false;
+ }
+
+ if (dest->Write(temp.data(), read, i) != read) {
+ return false;
+ }
+
+ progress.UpdateFileProgress(i);
+ }
+
+ progress.FinishDownloadingFile();
+
+ return true;
+}
+
+bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
+ ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+
+ for (const auto& file : src->GetFiles()) {
+ const auto out_file = VfsCreateFileWrap(dest, file->GetName());
+ if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
+ return false;
+ }
+ }
+ progress.CommitDirectory(src->GetName());
+
+ return true;
+}
+
+bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
+ ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+
+ for (const auto& dir : src->GetSubdirectories()) {
+ const auto out = dest->CreateSubdirectory(dir->GetName());
+ if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // Anonymous namespace
+
+class Boxcat::Client {
+public:
+ Client(std::string path, u64 title_id, u64 build_id)
+ : path(std::move(path)), title_id(title_id), build_id(build_id) {}
+
+ DownloadResult DownloadDataZip() {
+ return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
+ "application/zip");
+ }
+
+ DownloadResult DownloadLaunchParam() {
+ return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
+ TIMEOUT_SECONDS / 3, "application/octet-stream");
+ }
+
+private:
+ DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
+ const std::string& content_type_name) {
+ if (client == nullptr) {
+ client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
+ }
+
+ httplib::Headers headers{
+ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
+ {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
+ {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
+ };
+
+ if (FileUtil::Exists(path)) {
+ FileUtil::IOFile file{path, "rb"};
+ if (file.IsOpen()) {
+ std::vector<u8> bytes(file.GetSize());
+ file.ReadBytes(bytes.data(), bytes.size());
+ const auto digest = DigestFile(bytes);
+ headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
+ }
+ }
+
+ const auto response = client->Get(resolved_path.c_str(), headers);
+ if (response == nullptr)
+ return DownloadResult::NoResponse;
+
+ if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
+ return DownloadResult::Success;
+ if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
+ return DownloadResult::BadClientVersion;
+ if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
+ return DownloadResult::NoMatchTitleId;
+ if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
+ return DownloadResult::NoMatchBuildId;
+ if (response->status != static_cast<int>(ResponseStatus::Ok))
+ return DownloadResult::GeneralWebError;
+
+ const auto content_type = response->headers.find("content-type");
+ if (content_type == response->headers.end() ||
+ content_type->second.find(content_type_name) == std::string::npos) {
+ return DownloadResult::InvalidContentType;
+ }
+
+ FileUtil::CreateFullPath(path);
+ FileUtil::IOFile file{path, "wb"};
+ if (!file.IsOpen())
+ return DownloadResult::GeneralFSError;
+ if (!file.Resize(response->body.size()))
+ return DownloadResult::GeneralFSError;
+ if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
+ return DownloadResult::GeneralFSError;
+
+ return DownloadResult::Success;
+ }
+
+ using Digest = std::array<u8, 0x20>;
+ static Digest DigestFile(std::vector<u8> bytes) {
+ Digest out{};
+ mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0);
+ return out;
+ }
+
+ std::unique_ptr<httplib::Client> client;
+ std::string path;
+ u64 title_id;
+ u64 build_id;
+};
+
+Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
+
+Boxcat::~Boxcat() = default;
+
+void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
+ ProgressServiceBackend& progress,
+ std::optional<std::string> dir_name = {}) {
+ progress.SetNeedHLELock(true);
+
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
+ const auto dir = dir_getter(title.title_id);
+ if (dir)
+ progress.SetTotalSize(dir->GetSize());
+ progress.FinishDownload(RESULT_SUCCESS);
+ return;
+ }
+
+ const auto zip_path{GetZIPFilePath(title.title_id)};
+ Boxcat::Client client{zip_path, title.title_id, title.build_id};
+
+ progress.StartConnecting();
+
+ const auto res = client.DownloadDataZip();
+ if (res != DownloadResult::Success) {
+ LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
+
+ if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
+ FileUtil::Delete(zip_path);
+ }
+
+ HandleDownloadDisplayResult(res);
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ progress.StartProcessingDataList();
+
+ FileUtil::IOFile zip{zip_path, "rb"};
+ const auto size = zip.GetSize();
+ std::vector<u8> bytes(size);
+ if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
+ if (extracted == nullptr) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ if (dir_name == std::nullopt) {
+ progress.SetTotalSize(extracted->GetSize());
+
+ const auto target_dir = dir_getter(title.title_id);
+ if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+ } else {
+ const auto target_dir = dir_getter(title.title_id);
+ if (target_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+
+ const auto target_sub = target_dir->GetSubdirectory(*dir_name);
+ const auto source_sub = extracted->GetSubdirectory(*dir_name);
+
+ progress.SetTotalSize(source_sub->GetSize());
+
+ std::vector<std::string> filenames;
+ {
+ const auto files = target_sub->GetFiles();
+ std::transform(files.begin(), files.end(), std::back_inserter(filenames),
+ [](const auto& vfile) { return vfile->GetName(); });
+ }
+
+ for (const auto& filename : filenames) {
+ VfsDeleteFileWrap(target_sub, filename);
+ }
+
+ if (target_sub == nullptr || source_sub == nullptr ||
+ !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+ progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
+ return;
+ }
+ }
+
+ progress.FinishDownload(RESULT_SUCCESS);
+}
+
+bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
+ is_syncing.exchange(true);
+ std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
+ .detach();
+ return true;
+}
+
+bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) {
+ is_syncing.exchange(true);
+ std::thread(
+ [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
+ .detach();
+ return true;
+}
+
+bool Boxcat::Clear(u64 title_id) {
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
+ return true;
+ }
+
+ const auto dir = dir_getter(title_id);
+
+ std::vector<std::string> dirnames;
+
+ for (const auto& subdir : dir->GetSubdirectories())
+ dirnames.push_back(subdir->GetName());
+
+ for (const auto& subdir : dirnames) {
+ if (!dir->DeleteSubdirectoryRecursive(subdir))
+ return false;
+ }
+
+ return true;
+}
+
+void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
+ Common::HexToString(passphrase));
+}
+
+std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
+ const auto path{GetBINFilePath(title.title_id)};
+
+ if (Settings::values.bcat_boxcat_local) {
+ LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
+ } else {
+ Boxcat::Client client{path, title.title_id, title.build_id};
+
+ const auto res = client.DownloadLaunchParam();
+ if (res != DownloadResult::Success) {
+ LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
+
+ if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
+ FileUtil::Delete(path);
+ }
+
+ HandleDownloadDisplayResult(res);
+ return std::nullopt;
+ }
+ }
+
+ FileUtil::IOFile bin{path, "rb"};
+ const auto size = bin.GetSize();
+ std::vector<u8> bytes(size);
+ if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
+ LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
+ path);
+ return std::nullopt;
+ }
+
+ return bytes;
+}
+
+Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
+ std::map<std::string, EventStatus>& games) {
+ httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT),
+ static_cast<int>(TIMEOUT_SECONDS)};
+
+ httplib::Headers headers{
+ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
+ {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
+ };
+
+ const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
+ if (response == nullptr)
+ return StatusResult::Offline;
+
+ if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
+ return StatusResult::BadClientVersion;
+
+ try {
+ nlohmann::json json = nlohmann::json::parse(response->body);
+
+ if (!json["online"].get<bool>())
+ return StatusResult::Offline;
+
+ if (json["global"].is_null())
+ global = std::nullopt;
+ else
+ global = json["global"].get<std::string>();
+
+ if (json["games"].is_array()) {
+ for (const auto object : json["games"]) {
+ if (object.is_object() && object.find("name") != object.end()) {
+ EventStatus detail{};
+ if (object["header"].is_string()) {
+ detail.header = object["header"].get<std::string>();
+ } else {
+ detail.header = std::nullopt;
+ }
+
+ if (object["footer"].is_string()) {
+ detail.footer = object["footer"].get<std::string>();
+ } else {
+ detail.footer = std::nullopt;
+ }
+
+ if (object["events"].is_array()) {
+ for (const auto& event : object["events"]) {
+ if (!event.is_string())
+ continue;
+ detail.events.push_back(event.get<std::string>());
+ }
+ }
+
+ games.insert_or_assign(object["name"], std::move(detail));
+ }
+ }
+ }
+
+ return StatusResult::Success;
+ } catch (const nlohmann::json::parse_error& error) {
+ LOG_ERROR(Service_BCAT, "{}", error.what());
+ return StatusResult::ParseError;
+ }
+}
+
+} // 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 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <optional>
+#include "core/hle/service/bcat/backend/backend.h"
+
+namespace Service::BCAT {
+
+struct EventStatus {
+ std::optional<std::string> header;
+ std::optional<std::string> footer;
+ std::vector<std::string> events;
+};
+
+/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and
+/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
+class Boxcat final : public Backend {
+ friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
+ ProgressServiceBackend& progress,
+ std::optional<std::string> dir_name);
+
+public:
+ explicit Boxcat(DirectoryGetter getter);
+ ~Boxcat() override;
+
+ bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
+ bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+ ProgressServiceBackend& progress) override;
+
+ bool Clear(u64 title_id) override;
+
+ void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
+
+ std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
+
+ enum class StatusResult {
+ Success,
+ Offline,
+ ParseError,
+ BadClientVersion,
+ };
+
+ static StatusResult GetStatus(std::optional<std::string>& global,
+ std::map<std::string, EventStatus>& games);
+
+private:
+ std::atomic_bool is_syncing{false};
+
+ class Client;
+ std::unique_ptr<Client> client;
+};
+
+} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 179aa4949..8bb2528c9 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -6,11 +6,16 @@
namespace Service::BCAT {
-BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
- : Module::Interface(std::move(module), name) {
+BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module,
+ FileSystem::FileSystemController& fsc, const char* name)
+ : Interface(system, std::move(module), fsc, name) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &BCAT::CreateBcatService, "CreateBcatService"},
+ {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
+ {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
};
+ // clang-format on
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 802bd689a..6354465fc 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -6,11 +6,16 @@
#include "core/hle/service/bcat/module.h"
+namespace Core {
+class System;
+}
+
namespace Service::BCAT {
class BCAT final : public Module::Interface {
public:
- explicit BCAT(std::shared_ptr<Module> module, const char* name);
+ explicit BCAT(Core::System& system, std::shared_ptr<Module> module,
+ FileSystem::FileSystemController& fsc, const char* name);
~BCAT() override;
};
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index b7bd738fc..4e4aa758b 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -2,34 +2,257 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cctype>
+#include <mbedtls/md5.h>
+#include "backend/boxcat.h"
+#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/bcat/module.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/settings.h"
namespace Service::BCAT {
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
+constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
+constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
+constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
+
+// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
+// and if any of them have a non-zero result it just forwards that result. This is the FS error code
+// for permission denied, which is the closest approximation of this scenario.
+constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
+
+using BCATDigest = std::array<u8, 0x10>;
+
+namespace {
+
+u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
+ u64 out{};
+ std::memcpy(&out, id.data(), sizeof(u64));
+ return out;
+}
+
+// The digest is only used to determine if a file is unique compared to others of the same name.
+// Since the algorithm isn't ever checked in game, MD5 is safe.
+BCATDigest DigestFile(const FileSys::VirtualFile& file) {
+ BCATDigest out{};
+ const auto bytes = file->ReadAllBytes();
+ mbedtls_md5(bytes.data(), bytes.size(), out.data());
+ return out;
+}
+
+// For a name to be valid it must be non-empty, must have a null terminating character as the final
+// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
+// file.
+bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
+ char match_char) {
+ const auto null_chars = std::count(name.begin(), name.end(), 0);
+ const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
+ return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
+ });
+ if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
+ LOG_ERROR(Service_BCAT, "Name passed was invalid!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return false;
+ }
+
+ return true;
+}
+
+bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
+ return VerifyNameValidInternal(ctx, name, '-');
+}
+
+bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
+ return VerifyNameValidInternal(ctx, name, '.');
+}
+
+} // Anonymous namespace
+
+struct DeliveryCacheDirectoryEntry {
+ FileName name;
+ u64 size;
+ BCATDigest digest;
+};
+
+class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
+public:
+ IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
+ const DeliveryCacheProgressImpl& impl)
+ : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
+ {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void GetEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(event);
+ }
+
+ void GetImpl(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Kernel::SharedPtr<Kernel::ReadableEvent> event;
+ const DeliveryCacheProgressImpl& impl;
+};
+
class IBcatService final : public ServiceFramework<IBcatService> {
public:
- IBcatService() : ServiceFramework("IBcatService") {
+ explicit IBcatService(Core::System& system_, Backend& backend_)
+ : ServiceFramework("IBcatService"), system{system_}, backend{backend_} {
+ // clang-format off
static const FunctionInfo functions[] = {
- {10100, nullptr, "RequestSyncDeliveryCache"},
- {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"},
+ {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"},
+ {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"},
{10200, nullptr, "CancelSyncDeliveryCacheRequest"},
{20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"},
{20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"},
- {30100, nullptr, "SetPassphrase"},
+ {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
{30200, nullptr, "RegisterBackgroundDeliveryTask"},
{30201, nullptr, "UnregisterBackgroundDeliveryTask"},
{30202, nullptr, "BlockDeliveryTask"},
{30203, nullptr, "UnblockDeliveryTask"},
{90100, nullptr, "EnumerateBackgroundDeliveryTask"},
{90200, nullptr, "GetDeliveryList"},
- {90201, nullptr, "ClearDeliveryCacheStorage"},
+ {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
{90300, nullptr, "GetPushNotificationLog"},
};
+ // clang-format on
RegisterHandlers(functions);
}
+
+private:
+ enum class SyncType {
+ Normal,
+ Directory,
+ Count,
+ };
+
+ std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
+ auto& backend{progress.at(static_cast<std::size_t>(type))};
+ return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
+ backend.GetImpl());
+ }
+
+ void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ backend.Synchronize({system.CurrentProcess()->GetTitleID(),
+ GetCurrentBuildID(system.GetCurrentProcessBuildID())},
+ progress.at(static_cast<std::size_t>(SyncType::Normal)));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
+ }
+
+ void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto name_raw = rp.PopRaw<DirectoryName>();
+ const auto name =
+ Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, name={}", name);
+
+ backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(),
+ GetCurrentBuildID(system.GetCurrentProcessBuildID())},
+ name,
+ progress.at(static_cast<std::size_t>(SyncType::Directory)));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
+ }
+
+ void SetPassphrase(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ const auto passphrase_raw = ctx.ReadBuffer();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
+ Common::HexToString(passphrase_raw));
+
+ if (title_id == 0) {
+ LOG_ERROR(Service_BCAT, "Invalid title ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ }
+
+ if (passphrase_raw.size() > 0x40) {
+ LOG_ERROR(Service_BCAT, "Passphrase too large!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ Passphrase passphrase{};
+ std::memcpy(passphrase.data(), passphrase_raw.data(),
+ std::min(passphrase.size(), passphrase_raw.size()));
+
+ backend.SetPassphrase(title_id, passphrase);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
+
+ if (title_id == 0) {
+ LOG_ERROR(Service_BCAT, "Invalid title ID!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ if (!backend.Clear(title_id)) {
+ LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_CLEAR_CACHE);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Core::System& system;
+ Backend& backend;
+
+ std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
+ ProgressServiceBackend{"Normal"},
+ ProgressServiceBackend{"Directory"},
+ };
};
void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
@@ -37,20 +260,332 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IBcatService>();
+ rb.PushIpcInterface<IBcatService>(system, *backend);
+}
+
+class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
+public:
+ IDeliveryCacheFileService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheFileService::Open, "Open"},
+ {1, &IDeliveryCacheFileService::Read, "Read"},
+ {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
+ {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto dir_name_raw = rp.PopRaw<DirectoryName>();
+ const auto file_name_raw = rp.PopRaw<FileName>();
+
+ const auto dir_name =
+ Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
+ const auto file_name =
+ Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
+
+ if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
+ return;
+
+ if (current_file != nullptr) {
+ LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_ENTITY_ALREADY_OPEN);
+ return;
+ }
+
+ const auto dir = root->GetSubdirectory(dir_name);
+
+ if (dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ current_file = dir->GetFile(file_name);
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto offset{rp.PopRaw<u64>()};
+
+ auto size = ctx.GetWriteBufferSize();
+
+ LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ size = std::min<u64>(current_file->GetSize() - offset, size);
+ const auto buffer = current_file->ReadBytes(size, offset);
+ ctx.WriteBuffer(buffer);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(buffer.size());
+ }
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(current_file->GetSize());
+ }
+
+ void GetDigest(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_file == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no file currently open!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(DigestFile(current_file));
+ }
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualFile current_file;
+};
+
+class IDeliveryCacheDirectoryService final
+ : public ServiceFramework<IDeliveryCacheDirectoryService> {
+public:
+ IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheDirectoryService::Open, "Open"},
+ {1, &IDeliveryCacheDirectoryService::Read, "Read"},
+ {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Open(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto name_raw = rp.PopRaw<DirectoryName>();
+ const auto name =
+ Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
+
+ LOG_DEBUG(Service_BCAT, "called, name={}", name);
+
+ if (!VerifyNameValidDir(ctx, name_raw))
+ return;
+
+ if (current_dir != nullptr) {
+ LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_ENTITY_ALREADY_OPEN);
+ return;
+ }
+
+ current_dir = root->GetSubdirectory(name);
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_FAILED_OPEN_ENTITY);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Read(Kernel::HLERequestContext& ctx) {
+ auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
+
+ LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no open directory!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ return;
+ }
+
+ const auto files = current_dir->GetFiles();
+ write_size = std::min<u64>(write_size, files.size());
+ std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
+ std::transform(
+ files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
+ FileName name{};
+ std::memcpy(name.data(), file->GetName().data(),
+ std::min(file->GetName().size(), name.size()));
+ return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
+ });
+
+ ctx.WriteBuffer(entries);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)));
+ }
+
+ void GetCount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ if (current_dir == nullptr) {
+ LOG_ERROR(Service_BCAT, "There is no open directory!");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_NO_OPEN_ENTITY);
+ return;
+ }
+
+ const auto files = current_dir->GetFiles();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(files.size()));
+ }
+
+ FileSys::VirtualDir root;
+ FileSys::VirtualDir current_dir;
+};
+
+class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
+public:
+ IDeliveryCacheStorageService(FileSys::VirtualDir root_)
+ : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
+ {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
+ {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ for (const auto& subdir : root->GetSubdirectories()) {
+ DirectoryName name{};
+ std::memcpy(name.data(), subdir->GetName().data(),
+ std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
+ entries.push_back(name);
+ }
+ }
+
+private:
+ void CreateFileService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheFileService>(root);
+ }
+
+ void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
+ }
+
+ void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
+ auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
+
+ LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
+
+ size = std::min<u64>(size, entries.size() - next_read_index);
+ ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
+ next_read_index += size;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(static_cast<u32>(size));
+ }
+
+ FileSys::VirtualDir root;
+ std::vector<DirectoryName> entries;
+ u64 next_read_index = 0;
+};
+
+void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_BCAT, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(
+ fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID()));
+}
+
+void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
+ Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto title_id = rp.PopRaw<u64>();
+
+ LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
+}
+
+std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
+ const auto backend = Settings::values.bcat_backend;
+
+#ifdef YUZU_ENABLE_BOXCAT
+ if (backend == "boxcat")
+ return std::make_unique<Boxcat>(std::move(getter));
+#endif
+
+ return std::make_unique<NullBackend>(std::move(getter));
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ FileSystem::FileSystemController& fsc_, const char* name)
+ : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)},
+ backend{CreateBackendFromSettings([&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })},
+ system{system_} {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager);
- std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager);
+ std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s")
+ ->InstallAsService(system.ServiceManager());
}
} // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index f0d63cab0..e4ba23ba0 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -6,23 +6,46 @@
#include "core/hle/service/service.h"
-namespace Service::BCAT {
+namespace Core {
+class System;
+}
+
+namespace Service {
+
+namespace FileSystem {
+class FileSystemController;
+} // namespace FileSystem
+
+namespace BCAT {
+
+class Backend;
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ FileSystem::FileSystemController& fsc_, const char* name);
~Interface() override;
void CreateBcatService(Kernel::HLERequestContext& ctx);
+ void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
+ void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
protected:
+ FileSystem::FileSystemController& fsc;
+
std::shared_ptr<Module> module;
+ std::unique_ptr<Backend> backend;
+
+ private:
+ Core::System& system;
};
};
/// Registers all BCAT services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
+
+} // namespace BCAT
-} // namespace Service::BCAT
+} // namespace Service
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index b2ebf6240..2546d7595 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -66,7 +66,7 @@ enum class FatalType : u32 {
static void GenerateErrorReport(Core::System& system, ResultCode error_code,
const FatalInfo& info) {
- const auto title_id = Core::CurrentProcess()->GetTitleID();
+ const auto title_id = system.CurrentProcess()->GetTitleID();
std::string crash_report = fmt::format(
"Yuzu {}-{} crash report\n"
"Title ID: {:016x}\n"
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 14cd0e322..11e5c56b7 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -241,7 +241,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
return FileSys::ERROR_PATH_NOT_FOUND;
}
-FileSystemController::FileSystemController() = default;
+FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}
FileSystemController::~FileSystemController() = default;
@@ -290,7 +290,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()
return ResultCode(-1);
}
- return romfs_factory->OpenCurrentProcess();
+ return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());
}
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
@@ -447,10 +447,10 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy
FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE};
FileSys::NACP nacp;
- const auto res = Core::System::GetInstance().GetAppLoader().ReadControlData(nacp);
+ const auto res = system.GetAppLoader().ReadControlData(nacp);
if (res != Loader::ResultStatus::Success) {
- FileSys::PatchManager pm{Core::CurrentProcess()->GetTitleID()};
+ FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
auto [nacp_unique, discard] = pm.GetControlMetadata();
if (nacp_unique != nullptr) {
@@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
return bis_factory->GetModificationDumpRoot(title_id);
}
+FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
+ LOG_TRACE(Service_FS, "Opening BCAT root for tid={:016X}", title_id);
+
+ if (bis_factory == nullptr)
+ return nullptr;
+
+ return bis_factory->GetBCATDirectory(title_id);
+}
+
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (overwrite) {
bis_factory = nullptr;
@@ -693,10 +702,10 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
if (bis_factory == nullptr) {
bis_factory =
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
- Core::System::GetInstance().RegisterContentProvider(
- FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
- Core::System::GetInstance().RegisterContentProvider(
- FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
+ system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
+ bis_factory->GetSystemNANDContents());
+ system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
+ bis_factory->GetUserNANDContents());
}
if (save_data_factory == nullptr) {
@@ -705,8 +714,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
if (sdmc_factory == nullptr) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
- Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
- sdmc_factory->GetSDMCContents());
+ system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
+ sdmc_factory->GetSDMCContents());
}
}
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3e0c03ec0..1b0a6a949 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -10,6 +10,10 @@
#include "core/file_sys/vfs.h"
#include "core/hle/result.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class BISFactory;
class RegisteredCache;
@@ -52,7 +56,7 @@ enum class ImageDirectoryId : u32 {
class FileSystemController {
public:
- FileSystemController();
+ explicit FileSystemController(Core::System& system_);
~FileSystemController();
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
@@ -110,6 +114,8 @@ public:
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
+ FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
+
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
@@ -123,6 +129,8 @@ private:
std::unique_ptr<FileSys::XCI> gamecard;
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
+
+ Core::System& system;
};
void InstallInterfaces(Core::System& system);
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index eb982ad49..cbd5466c1 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -803,7 +803,7 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
- auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
+ [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
u128 uid = rp.PopRaw<u128>();
LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 42b4ee861..75dd9043b 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -237,7 +237,6 @@ private:
};
Common::UUID uuid;
- bool is_event_created = false;
Kernel::EventPair notification_event;
std::queue<SizedNotificationInfo> notifications;
States states{};
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
index 8e8263f5b..1f2131ec8 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.cpp
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -11,11 +11,10 @@
namespace Service::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
-constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
+[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
-Controller_DebugPad::Controller_DebugPad(Core::System& system)
- : ControllerBase(system), system(system) {}
+Controller_DebugPad::Controller_DebugPad(Core::System& system) : ControllerBase(system) {}
Controller_DebugPad::~Controller_DebugPad() = default;
void Controller_DebugPad::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index 6c4de817e..555b29d76 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -89,6 +89,5 @@ private:
buttons;
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>
analogs;
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
index 80da0a0d3..6e990dd00 100644
--- a/src/core/hle/service/hid/controllers/gesture.cpp
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -10,8 +10,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
-Controller_Gesture::Controller_Gesture(Core::System& system)
- : ControllerBase(system), system(system) {}
+Controller_Gesture::Controller_Gesture(Core::System& system) : ControllerBase(system) {}
Controller_Gesture::~Controller_Gesture() = default;
void Controller_Gesture::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
index 396897527..f650b8338 100644
--- a/src/core/hle/service/hid/controllers/gesture.h
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -59,6 +59,5 @@ private:
std::array<GestureState, 17> gesture_states;
};
SharedMemory shared_memory{};
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index e587b2e15..358cb9329 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -12,8 +12,7 @@ namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
constexpr u8 KEYS_PER_BYTE = 8;
-Controller_Keyboard::Controller_Keyboard(Core::System& system)
- : ControllerBase(system), system(system) {}
+Controller_Keyboard::Controller_Keyboard(Core::System& system) : ControllerBase(system) {}
Controller_Keyboard::~Controller_Keyboard() = default;
void Controller_Keyboard::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
index ef586f7eb..f3eef5936 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -53,6 +53,5 @@ private:
keyboard_keys;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods>
keyboard_mods;
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index 88f2ca4c1..93d88ea50 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -11,7 +11,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
-Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(system) {}
+Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system) {}
Controller_Mouse::~Controller_Mouse() = default;
void Controller_Mouse::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
index df2da6ae3..357ab7107 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -53,6 +53,5 @@ private:
std::unique_ptr<Input::MouseDevice> mouse_device;
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons>
mouse_button_devices;
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 44b668fbf..a2b25a796 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -20,7 +20,7 @@
namespace Service::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
-constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
+[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
constexpr u32 MAX_NPAD_ID = 7;
@@ -105,6 +105,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.joy_styles.raw = 0; // Zero out
controller.device_type.raw = 0;
switch (controller_type) {
+ case NPadControllerType::None:
+ UNREACHABLE();
case NPadControllerType::Handheld:
controller.joy_styles.handheld.Assign(1);
controller.device_type.handheld.Assign(1);
@@ -165,13 +167,14 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
controller.battery_level[2] = BATTERY_FULL;
+ styleset_changed_events[controller_idx].writable->Signal();
}
void Controller_NPad::OnInit() {
auto& kernel = system.Kernel();
for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
- kernel, Kernel::ResetType::Automatic, fmt::format("npad:NpadStyleSetChanged_{}", i));
+ kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i));
}
if (!IsControllerActivated()) {
@@ -238,7 +241,7 @@ void Controller_NPad::OnRelease() {}
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
const auto controller_idx = NPadIdToIndex(npad_id);
- const auto controller_type = connected_controllers[controller_idx].type;
+ [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type;
if (!connected_controllers[controller_idx].is_connected) {
return;
}
@@ -345,6 +348,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_entry.connection_status.raw = 0;
switch (controller_type) {
+ case NPadControllerType::None:
+ UNREACHABLE();
case NPadControllerType::Handheld:
handheld_entry.connection_status.raw = 0;
handheld_entry.connection_status.IsWired.Assign(1);
@@ -433,7 +438,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
std::memcpy(supported_npad_id_types.data(), data, length);
- bool had_controller_update = false;
for (std::size_t i = 0; i < connected_controllers.size(); i++) {
auto& controller = connected_controllers[i];
if (!controller.is_connected) {
@@ -452,10 +456,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
controller.type = requested_controller;
InitNewlyAddedControler(i);
}
- had_controller_update = true;
- }
- if (had_controller_update) {
- styleset_changed_events[i].writable->Signal();
}
}
}
@@ -481,7 +481,6 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
const std::size_t npad_index = NPadIdToIndex(npad_id);
ASSERT(npad_index < shared_memory_entries.size());
if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
- styleset_changed_events[npad_index].writable->Signal();
shared_memory_entries[npad_index].pad_assignment = assignment_mode;
}
}
@@ -507,7 +506,6 @@ Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEven
// TODO(ogniK): Figure out the best time to signal this event. This event seems that it should
// be signalled at least once, and signaled after a new controller is connected?
const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
- styleset_event.writable->Signal();
return styleset_event.readable;
}
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
index 9b829341e..9e527d176 100644
--- a/src/core/hle/service/hid/controllers/stubbed.cpp
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -9,8 +9,7 @@
namespace Service::HID {
-Controller_Stubbed::Controller_Stubbed(Core::System& system)
- : ControllerBase(system), system(system) {}
+Controller_Stubbed::Controller_Stubbed(Core::System& system) : ControllerBase(system) {}
Controller_Stubbed::~Controller_Stubbed() = default;
void Controller_Stubbed::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h
index 37d7d8538..4fa83ac85 100644
--- a/src/core/hle/service/hid/controllers/stubbed.h
+++ b/src/core/hle/service/hid/controllers/stubbed.h
@@ -30,6 +30,5 @@ public:
private:
bool smart_update{};
std::size_t common_offset{};
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 25912fd69..1c6e55566 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -13,8 +13,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
-Controller_Touchscreen::Controller_Touchscreen(Core::System& system)
- : ControllerBase(system), system(system) {}
+Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {}
Controller_Touchscreen::~Controller_Touchscreen() = default;
void Controller_Touchscreen::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index 3429c84db..a1d97269e 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -69,6 +69,5 @@ private:
TouchScreenSharedMemory shared_memory{};
std::unique_ptr<Input::TouchDevice> touch_device;
s64_le last_touch{};
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
index 1bce044b4..27511b27b 100644
--- a/src/core/hle/service/hid/controllers/xpad.cpp
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -10,7 +10,7 @@
namespace Service::HID {
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
-Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(system) {}
+Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system) {}
Controller_XPad::~Controller_XPad() = default;
void Controller_XPad::OnInit() {}
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
index c445ebec0..ad229787c 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -56,6 +56,5 @@ private:
};
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
SharedMemory shared_memory{};
- Core::System& system;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 8d76ba746..ba1da4181 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -38,8 +38,10 @@ namespace Service::HID {
// Updating period for each HID device.
// TODO(ogniK): Find actual polling rate of hid
constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66);
-constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
-constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
+[[maybe_unused]] constexpr s64 accelerometer_update_ticks =
+ static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
+[[maybe_unused]] constexpr s64 gyroscope_update_ticks =
+ static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100);
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system)
@@ -193,7 +195,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
{102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
{103, &Hid::ActivateNpad, "ActivateNpad"},
- {104, nullptr, "DeactivateNpad"},
+ {104, &Hid::DeactivateNpad, "DeactivateNpad"},
{106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
{107, &Hid::DisconnectNpad, "DisconnectNpad"},
{108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
@@ -468,6 +470,17 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {
applet_resource->ActivateController(HidController::NPad);
}
+void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ applet_resource->DeactivateController(HidController::NPad);
+}
+
void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto npad_id{rp.Pop<u32>()};
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 35b663679..01852e019 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -99,6 +99,7 @@ private:
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
void ActivateNpad(Kernel::HLERequestContext& ctx);
+ void DeactivateNpad(Kernel::HLERequestContext& ctx);
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
void DisconnectNpad(Kernel::HLERequestContext& ctx);
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 3164ca26e..499376bfc 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -163,7 +163,7 @@ public:
return;
}
- if (Core::CurrentProcess()->GetTitleID() != header.title_id) {
+ if (system.CurrentProcess()->GetTitleID() != header.title_id) {
LOG_ERROR(Service_LDR,
"Attempting to load NRR with title ID other than current process. (actual "
"{:016X})!",
@@ -327,7 +327,7 @@ public:
}
// Load NRO as new executable module
- auto* process = Core::CurrentProcess();
+ auto* process = system.CurrentProcess();
auto& vm_manager = process->VMManager();
auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size);
@@ -411,7 +411,7 @@ public:
return;
}
- auto& vm_manager = Core::CurrentProcess()->VMManager();
+ auto& vm_manager = system.CurrentProcess()->VMManager();
const auto& nro_info = iter->second;
// Unmap the mirrored memory
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index a42c22d44..aa886cd3e 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -18,8 +18,8 @@
namespace Service::NFP {
namespace ErrCodes {
-constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
- -1); // TODO(ogniK): Find the actual error code
+[[maybe_unused]] constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
+ -1); // TODO(ogniK): Find the actual error code
constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
} // namespace ErrCodes
@@ -35,7 +35,7 @@ Module::Interface::~Interface() = default;
class IUser final : public ServiceFramework<IUser> {
public:
IUser(Module::Interface& nfp_interface, Core::System& system)
- : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) {
+ : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
@@ -183,6 +183,8 @@ private:
case DeviceState::TagRemoved:
device_state = DeviceState::Initialized;
break;
+ default:
+ break;
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -324,7 +326,6 @@ private:
Kernel::EventPair deactivate_event;
Kernel::EventPair availability_change_event;
const Module::Interface& nfp_interface;
- Core::System& system;
};
void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 24d1813a7..756a2af57 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -12,6 +12,13 @@
namespace Service::NIFM {
+enum class RequestState : u32 {
+ NotSubmitted = 1,
+ Error = 1, ///< The duplicate 1 is intentional; it means both not submitted and error on HW.
+ Pending = 2,
+ Connected = 3,
+};
+
class IScanRequest final : public ServiceFramework<IScanRequest> {
public:
explicit IScanRequest() : ServiceFramework("IScanRequest") {
@@ -81,7 +88,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.PushEnum(RequestState::Connected);
}
void GetResult(Kernel::HLERequestContext& ctx) {
@@ -189,14 +196,14 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u8>(0);
+ rb.Push<u8>(1);
}
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u8>(0);
+ rb.Push<u8>(1);
}
Core::System& system;
};
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 7dcdb4a07..f64535237 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -324,14 +324,14 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
// Map backing memory for the font data
LOG_DEBUG(Service_NS, "called");
- Core::CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0,
- SHARED_FONT_MEM_SIZE,
- Kernel::MemoryState::Shared);
+ system.CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0,
+ SHARED_FONT_MEM_SIZE,
+ Kernel::MemoryState::Shared);
// Create shared font memory object
auto& kernel = system.Kernel();
impl->shared_font_mem = Kernel::SharedMemory::Create(
- kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
+ kernel, system.CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
"PL_U:shared_font_mem");
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 6bc053f27..07c88465e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -45,6 +45,8 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std:
return GetVARegions(input, output);
case IoctlCommand::IocUnmapBufferCommand:
return UnmapBuffer(input, output);
+ default:
+ break;
}
if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand)
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index ff6b1abae..eb88fee1b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -38,9 +38,10 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::v
return IocCtrlEventUnregister(input, output);
case IoctlCommand::IocCtrlEventSignalCommand:
return IocCtrlEventSignal(input, output);
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
+ return 0;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
}
u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 389ace76f..cc2192e5c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -40,9 +40,10 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
return FlushL2(input, output);
case IoctlCommand::IocGetGpuTime:
return GetGpuTime(input, output);
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented ioctl");
+ return 0;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
}
u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 2b8d1bef6..9de0ace22 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -44,6 +44,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
return GetWaitbase(input, output);
case IoctlCommand::IocChannelSetTimeoutCommand:
return ChannelSetTimeout(input, output);
+ default:
+ break;
}
if (command.group == NVGPU_IOCTL_MAGIC) {
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index c12a746c8..7c5302017 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
AOC::InstallInterfaces(*sm, system);
APM::InstallInterfaces(system);
Audio::InstallInterfaces(*sm, system);
- BCAT::InstallInterfaces(*sm);
+ BCAT::InstallInterfaces(system);
BPC::InstallInterfaces(*sm);
BtDrv::InstallInterfaces(*sm, system);
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,
// Apply cheats if they exist and the program has a valid title ID
if (pm) {
auto& system = Core::System::GetInstance();
+ system.SetCurrentProcessBuildID(nso_header.build_id);
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
if (!cheats.empty()) {
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 9e030789d..fa49f3dd0 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -146,7 +146,7 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
* using a VMA from the current process.
*/
static u8* GetPointerFromVMA(VAddr vaddr) {
- return GetPointerFromVMA(*Core::CurrentProcess(), vaddr);
+ return GetPointerFromVMA(*Core::System::GetInstance().CurrentProcess(), vaddr);
}
template <typename T>
@@ -226,7 +226,7 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
}
bool IsValidVirtualAddress(const VAddr vaddr) {
- return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr);
+ return IsValidVirtualAddress(*Core::System::GetInstance().CurrentProcess(), vaddr);
}
bool IsKernelVirtualAddress(const VAddr vaddr) {
@@ -387,7 +387,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
}
void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) {
- ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size);
+ ReadBlock(*Core::System::GetInstance().CurrentProcess(), src_addr, dest_buffer, size);
}
void Write8(const VAddr addr, const u8 data) {
@@ -450,7 +450,7 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
}
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) {
- WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size);
+ WriteBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_buffer, size);
}
void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) {
@@ -539,7 +539,7 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
}
void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) {
- CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size);
+ CopyBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_addr, size);
}
} // namespace Memory
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 7de3fd1e5..d1fc94060 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -103,6 +103,8 @@ void LogSettings() {
LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
+ LogSetting("Services_BCATBackend", Settings::values.bcat_backend);
+ LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local);
}
} // 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 {
bool reporting_services;
bool quest_flag;
+ // BCAT
+ std::string bcat_backend;
+ bool bcat_boxcat_local;
+
// WebService
bool enable_telemetry;
std::string web_api_url;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index e2f85c5f1..eaa694ff8 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -105,9 +105,15 @@ add_library(video_core STATIC
shader/decode/warp.cpp
shader/decode/xmad.cpp
shader/decode/other.cpp
+ shader/ast.cpp
+ shader/ast.h
shader/control_flow.cpp
shader/control_flow.h
+ shader/compiler_settings.cpp
+ shader/compiler_settings.h
shader/decode.cpp
+ shader/expr.cpp
+ shader/expr.h
shader/node_helper.cpp
shader/node_helper.h
shader/node.h
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 28272ef6f..7a6355ce2 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -544,7 +544,7 @@ enum class VoteOperation : u64 {
Eq = 2, // allThreadsEqualNV
};
-enum class ImageAtomicSize : u64 {
+enum class ImageAtomicOperationType : u64 {
U32 = 0,
S32 = 1,
U64 = 2,
@@ -1432,11 +1432,11 @@ union Instruction {
ASSERT(mode == SurfaceDataMode::D_BA);
return store_data_layout;
}
- } sust;
+ } suldst;
union {
BitField<28, 1, u64> is_ba;
- BitField<51, 3, ImageAtomicSize> size;
+ BitField<51, 3, ImageAtomicOperationType> operation_type;
BitField<33, 3, ImageType> image_type;
BitField<29, 4, ImageAtomicOperation> operation;
BitField<49, 2, OutOfBoundsStore> out_of_bounds_store;
@@ -1595,6 +1595,7 @@ public:
TMML_B, // Texture Mip Map Level
TMML, // Texture Mip Map Level
SUST, // Surface Store
+ SULD, // Surface Load
SUATOM, // Surface Atomic Operation
EXIT,
NOP,
@@ -1884,6 +1885,7 @@ private:
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
+ INST("11101011000-----", Id::SULD, Type::Image, "SULD"),
INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),
INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"),
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 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cstddef>
+#include <vector>
#include <glad/glad.h>
#include "common/logging/log.h"
@@ -30,9 +32,27 @@ bool TestProgram(const GLchar* glsl) {
return link_status == GL_TRUE;
}
+std::vector<std::string_view> GetExtensions() {
+ GLint num_extensions;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+ std::vector<std::string_view> extensions;
+ extensions.reserve(num_extensions);
+ for (GLint index = 0; index < num_extensions; ++index) {
+ extensions.push_back(
+ reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index))));
+ }
+ return extensions;
+}
+
+bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) {
+ return std::find(images.begin(), images.end(), extension) != images.end();
+}
+
} // Anonymous namespace
Device::Device() {
+ const std::vector extensions = GetExtensions();
+
uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
@@ -40,6 +60,7 @@ Device::Device() {
has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
GLAD_GL_NV_shader_thread_shuffle;
has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
+ has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
has_variable_aoffi = TestVariableAoffi();
has_component_indexing_bug = TestComponentIndexingBug();
has_precise_bug = TestPreciseBug();
@@ -55,6 +76,7 @@ Device::Device(std::nullptr_t) {
max_varyings = 15;
has_warp_intrinsics = true;
has_vertex_viewport_layer = true;
+ has_image_load_formatted = true;
has_variable_aoffi = true;
has_component_indexing_bug = false;
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:
return has_vertex_viewport_layer;
}
+ bool HasImageLoadFormatted() const {
+ return has_image_load_formatted;
+ }
+
bool HasVariableAoffi() const {
return has_variable_aoffi;
}
@@ -61,6 +65,7 @@ private:
u32 max_varyings{};
bool has_warp_intrinsics{};
bool has_vertex_viewport_layer{};
+ bool has_image_load_formatted{};
bool has_variable_aoffi{};
bool has_component_indexing_bug{};
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
const auto primitive_mode{variant.primitive_mode};
const auto texture_buffer_usage{variant.texture_buffer_usage};
- std::string source = "#version 430 core\n"
- "#extension GL_ARB_separate_shader_objects : enable\n"
- "#extension GL_NV_gpu_shader5 : enable\n"
- "#extension GL_NV_shader_thread_group : enable\n"
- "#extension GL_NV_shader_thread_shuffle : enable\n";
- if (entries.shader_viewport_layer_array) {
- source += "#extension GL_ARB_shader_viewport_layer_array : enable\n";
- }
+ std::string source = R"(#version 430 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shader_viewport_layer_array : enable
+#extension GL_EXT_shader_image_load_formatted : enable
+#extension GL_NV_gpu_shader5 : enable
+#extension GL_NV_shader_thread_group : enable
+#extension GL_NV_shader_thread_shuffle : enable
+)";
if (program_type == ProgramType::Compute) {
source += "#extension GL_ARB_compute_variable_group_size : require\n";
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 74cb59bc1..6a610a3bc 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -19,6 +19,8 @@
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
+#include "video_core/shader/ast.h"
+#include "video_core/shader/node.h"
#include "video_core/shader/shader_ir.h"
namespace OpenGL::GLShader {
@@ -241,6 +243,26 @@ constexpr const char* GetTypeString(Type type) {
}
}
+constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) {
+ switch (image_type) {
+ case Tegra::Shader::ImageType::Texture1D:
+ return "1D";
+ case Tegra::Shader::ImageType::TextureBuffer:
+ return "Buffer";
+ case Tegra::Shader::ImageType::Texture1DArray:
+ return "1DArray";
+ case Tegra::Shader::ImageType::Texture2D:
+ return "2D";
+ case Tegra::Shader::ImageType::Texture2DArray:
+ return "2DArray";
+ case Tegra::Shader::ImageType::Texture3D:
+ return "3D";
+ default:
+ UNREACHABLE();
+ return "1D";
+ }
+}
+
/// Generates code to use for a swizzle operation.
constexpr const char* GetSwizzle(u32 element) {
constexpr std::array swizzle = {".x", ".y", ".z", ".w"};
@@ -313,39 +335,24 @@ constexpr bool IsVertexShader(ProgramType stage) {
return stage == ProgramType::VertexA || stage == ProgramType::VertexB;
}
+class ASTDecompiler;
+class ExprDecompiler;
+
class GLSLDecompiler final {
public:
explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage,
std::string suffix)
: device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {}
- void Decompile() {
- DeclareVertex();
- DeclareGeometry();
- DeclareRegisters();
- DeclarePredicates();
- DeclareLocalMemory();
- DeclareSharedMemory();
- DeclareInternalFlags();
- DeclareInputAttributes();
- DeclareOutputAttributes();
- DeclareConstantBuffers();
- DeclareGlobalMemory();
- DeclareSamplers();
- DeclarePhysicalAttributeReader();
- DeclareImages();
-
- code.AddLine("void execute_{}() {{", suffix);
- ++code.scope;
-
+ void DecompileBranchMode() {
// VM's program counter
const auto first_address = ir.GetBasicBlocks().begin()->first;
code.AddLine("uint jmp_to = {}U;", first_address);
// TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
// unlikely that shaders will use 20 nested SSYs and PBKs.
+ constexpr u32 FLOW_STACK_SIZE = 20;
if (!ir.IsFlowStackDisabled()) {
- constexpr u32 FLOW_STACK_SIZE = 20;
for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
@@ -371,10 +378,37 @@ public:
code.AddLine("default: return;");
code.AddLine("}}");
- for (std::size_t i = 0; i < 2; ++i) {
- --code.scope;
- code.AddLine("}}");
+ --code.scope;
+ code.AddLine("}}");
+ }
+
+ void DecompileAST();
+
+ void Decompile() {
+ DeclareVertex();
+ DeclareGeometry();
+ DeclareRegisters();
+ DeclarePredicates();
+ DeclareLocalMemory();
+ DeclareInternalFlags();
+ DeclareInputAttributes();
+ DeclareOutputAttributes();
+ DeclareConstantBuffers();
+ DeclareGlobalMemory();
+ DeclareSamplers();
+ DeclarePhysicalAttributeReader();
+
+ code.AddLine("void execute_{}() {{", suffix);
+ ++code.scope;
+
+ if (ir.IsDecompiled()) {
+ DecompileAST();
+ } else {
+ DecompileBranchMode();
}
+
+ --code.scope;
+ code.AddLine("}}");
}
std::string GetResult() {
@@ -398,13 +432,14 @@ public:
usage.is_read, usage.is_written);
}
entries.clip_distances = ir.GetClipDistances();
- entries.shader_viewport_layer_array =
- IsVertexShader(stage) && (ir.UsesLayer() || ir.UsesViewportIndex());
entries.shader_length = ir.GetLength();
return entries;
}
private:
+ friend class ASTDecompiler;
+ friend class ExprDecompiler;
+
void DeclareVertex() {
if (!IsVertexShader(stage))
return;
@@ -722,42 +757,6 @@ private:
void DeclareImages() {
const auto& images{ir.GetImages()};
for (const auto& [offset, image] : images) {
- const char* image_type = [&] {
- switch (image.GetType()) {
- case Tegra::Shader::ImageType::Texture1D:
- return "image1D";
- case Tegra::Shader::ImageType::TextureBuffer:
- return "imageBuffer";
- case Tegra::Shader::ImageType::Texture1DArray:
- return "image1DArray";
- case Tegra::Shader::ImageType::Texture2D:
- return "image2D";
- case Tegra::Shader::ImageType::Texture2DArray:
- return "image2DArray";
- case Tegra::Shader::ImageType::Texture3D:
- return "image3D";
- default:
- UNREACHABLE();
- return "image1D";
- }
- }();
-
- const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> {
- if (!image.IsSizeKnown()) {
- return {"", ""};
- }
- switch (image.GetSize()) {
- case Tegra::Shader::ImageAtomicSize::U32:
- return {"u", "r32ui, "};
- case Tegra::Shader::ImageAtomicSize::S32:
- return {"i", "r32i, "};
- default:
- UNIMPLEMENTED_MSG("Unimplemented atomic size={}",
- static_cast<u32>(image.GetSize()));
- return {"", ""};
- }
- }();
-
std::string qualifier = "coherent volatile";
if (image.IsRead() && !image.IsWritten()) {
qualifier += " readonly";
@@ -765,9 +764,10 @@ private:
qualifier += " writeonly";
}
- code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform "
- "{} {};",
- image.GetIndex(), qualifier, image_type, GetImage(image));
+ const char* format = image.IsAtomic() ? "r32ui, " : "";
+ const char* type_declaration = GetImageTypeDeclaration(image.GetType());
+ code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format,
+ image.GetIndex(), qualifier, type_declaration, GetImage(image));
}
if (!images.empty()) {
code.AddNewLine();
@@ -1234,28 +1234,13 @@ private:
}
std::string BuildImageValues(Operation operation) {
+ constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"};
const auto meta{std::get<MetaImage>(operation.GetMeta())};
- const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> {
- constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"};
- if (!meta.image.IsSizeKnown()) {
- return {float_constructors, Type::Float};
- }
- switch (meta.image.GetSize()) {
- case Tegra::Shader::ImageAtomicSize::U32:
- return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint};
- case Tegra::Shader::ImageAtomicSize::S32:
- return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint};
- default:
- UNIMPLEMENTED_MSG("Unimplemented image size={}",
- static_cast<u32>(meta.image.GetSize()));
- return {float_constructors, Type::Float};
- }
- }();
const std::size_t values_count{meta.values.size()};
std::string expr = fmt::format("{}(", constructors.at(values_count - 1));
for (std::size_t i = 0; i < values_count; ++i) {
- expr += Visit(meta.values.at(i)).As(type);
+ expr += Visit(meta.values.at(i)).AsUint();
if (i + 1 < values_count) {
expr += ", ";
}
@@ -1264,29 +1249,6 @@ private:
return expr;
}
- Expression AtomicImage(Operation operation, const char* opname) {
- constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
- const auto meta{std::get<MetaImage>(operation.GetMeta())};
- ASSERT(meta.values.size() == 1);
- ASSERT(meta.image.IsSizeKnown());
-
- const auto type = [&]() {
- switch (const auto size = meta.image.GetSize()) {
- case Tegra::Shader::ImageAtomicSize::U32:
- return Type::Uint;
- case Tegra::Shader::ImageAtomicSize::S32:
- return Type::Int;
- default:
- UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size));
- return Type::Uint;
- }
- }();
-
- return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image),
- BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)),
- type};
- }
-
Expression Assign(Operation operation) {
const Node& dest = operation[0];
const Node& src = operation[1];
@@ -1545,6 +1507,8 @@ private:
case Tegra::Shader::HalfType::H1_H1:
return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
}
+ UNREACHABLE();
+ return {"0", Type::Int};
}
Expression HMergeF32(Operation operation) {
@@ -1809,6 +1773,19 @@ private:
return {tmp, Type::Float};
}
+ Expression ImageLoad(Operation operation) {
+ if (!device.HasImageLoadFormatted()) {
+ LOG_ERROR(Render_OpenGL,
+ "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load");
+ return {"0", Type::Int};
+ }
+
+ const auto meta{std::get<MetaImage>(operation.GetMeta())};
+ return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image),
+ BuildIntegerCoordinates(operation), GetSwizzle(meta.element)),
+ Type::Uint};
+ }
+
Expression ImageStore(Operation operation) {
const auto meta{std::get<MetaImage>(operation.GetMeta())};
code.AddLine("imageStore({}, {}, {});", GetImage(meta.image),
@@ -1816,31 +1793,14 @@ private:
return {};
}
- Expression AtomicImageAdd(Operation operation) {
- return AtomicImage(operation, "imageAtomicAdd");
- }
-
- Expression AtomicImageMin(Operation operation) {
- return AtomicImage(operation, "imageAtomicMin");
- }
-
- Expression AtomicImageMax(Operation operation) {
- return AtomicImage(operation, "imageAtomicMax");
- }
- Expression AtomicImageAnd(Operation operation) {
- return AtomicImage(operation, "imageAtomicAnd");
- }
-
- Expression AtomicImageOr(Operation operation) {
- return AtomicImage(operation, "imageAtomicOr");
- }
-
- Expression AtomicImageXor(Operation operation) {
- return AtomicImage(operation, "imageAtomicXor");
- }
+ template <const std::string_view& opname>
+ Expression AtomicImage(Operation operation) {
+ const auto meta{std::get<MetaImage>(operation.GetMeta())};
+ ASSERT(meta.values.size() == 1);
- Expression AtomicImageExchange(Operation operation) {
- return AtomicImage(operation, "imageAtomicExchange");
+ return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image),
+ BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()),
+ Type::Uint};
}
Expression Branch(Operation operation) {
@@ -1877,10 +1837,9 @@ private:
return {};
}
- Expression Exit(Operation operation) {
+ void PreExit() {
if (stage != ProgramType::Fragment) {
- code.AddLine("return;");
- return {};
+ return;
}
const auto& used_registers = ir.GetRegisters();
const auto SafeGetRegister = [&](u32 reg) -> Expression {
@@ -1912,7 +1871,10 @@ private:
// already contains one past the last color register.
code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
}
+ }
+ Expression Exit(Operation operation) {
+ PreExit();
code.AddLine("return;");
return {};
}
@@ -2035,6 +1997,12 @@ private:
Func() = delete;
~Func() = delete;
+ static constexpr std::string_view Add = "Add";
+ static constexpr std::string_view And = "And";
+ static constexpr std::string_view Or = "Or";
+ static constexpr std::string_view Xor = "Xor";
+ static constexpr std::string_view Exchange = "Exchange";
+
static constexpr std::string_view ShuffleIndexed = "shuffleNV";
static constexpr std::string_view ShuffleUp = "shuffleUpNV";
static constexpr std::string_view ShuffleDown = "shuffleDownNV";
@@ -2172,14 +2140,14 @@ private:
&GLSLDecompiler::TextureQueryLod,
&GLSLDecompiler::TexelFetch,
+ &GLSLDecompiler::ImageLoad,
&GLSLDecompiler::ImageStore,
- &GLSLDecompiler::AtomicImageAdd,
- &GLSLDecompiler::AtomicImageMin,
- &GLSLDecompiler::AtomicImageMax,
- &GLSLDecompiler::AtomicImageAnd,
- &GLSLDecompiler::AtomicImageOr,
- &GLSLDecompiler::AtomicImageXor,
- &GLSLDecompiler::AtomicImageExchange,
+
+ &GLSLDecompiler::AtomicImage<Func::Add>,
+ &GLSLDecompiler::AtomicImage<Func::And>,
+ &GLSLDecompiler::AtomicImage<Func::Or>,
+ &GLSLDecompiler::AtomicImage<Func::Xor>,
+ &GLSLDecompiler::AtomicImage<Func::Exchange>,
&GLSLDecompiler::Branch,
&GLSLDecompiler::BranchIndirect,
@@ -2303,6 +2271,208 @@ private:
ShaderWriter code;
};
+static constexpr std::string_view flow_var = "flow_var_";
+
+std::string GetFlowVariable(u32 i) {
+ return fmt::format("{}{}", flow_var, i);
+}
+
+class ExprDecompiler {
+public:
+ explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
+
+ void operator()(VideoCommon::Shader::ExprAnd& expr) {
+ inner += "( ";
+ std::visit(*this, *expr.operand1);
+ inner += " && ";
+ std::visit(*this, *expr.operand2);
+ inner += ')';
+ }
+
+ void operator()(VideoCommon::Shader::ExprOr& expr) {
+ inner += "( ";
+ std::visit(*this, *expr.operand1);
+ inner += " || ";
+ std::visit(*this, *expr.operand2);
+ inner += ')';
+ }
+
+ void operator()(VideoCommon::Shader::ExprNot& expr) {
+ inner += '!';
+ std::visit(*this, *expr.operand1);
+ }
+
+ void operator()(VideoCommon::Shader::ExprPredicate& expr) {
+ const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
+ inner += decomp.GetPredicate(pred);
+ }
+
+ void operator()(VideoCommon::Shader::ExprCondCode& expr) {
+ const Node cc = decomp.ir.GetConditionCode(expr.cc);
+ std::string target;
+
+ if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
+ const auto index = pred->GetIndex();
+ switch (index) {
+ case Tegra::Shader::Pred::NeverExecute:
+ target = "false";
+ case Tegra::Shader::Pred::UnusedIndex:
+ target = "true";
+ default:
+ target = decomp.GetPredicate(index);
+ }
+ } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
+ target = decomp.GetInternalFlag(flag->GetFlag());
+ } else {
+ UNREACHABLE();
+ }
+ inner += target;
+ }
+
+ void operator()(VideoCommon::Shader::ExprVar& expr) {
+ inner += GetFlowVariable(expr.var_index);
+ }
+
+ void operator()(VideoCommon::Shader::ExprBoolean& expr) {
+ inner += expr.value ? "true" : "false";
+ }
+
+ std::string& GetResult() {
+ return inner;
+ }
+
+private:
+ std::string inner;
+ GLSLDecompiler& decomp;
+};
+
+class ASTDecompiler {
+public:
+ explicit ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
+
+ void operator()(VideoCommon::Shader::ASTProgram& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(VideoCommon::Shader::ASTIfThen& ast) {
+ ExprDecompiler expr_parser{decomp};
+ std::visit(expr_parser, *ast.condition);
+ decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
+ decomp.code.scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ decomp.code.scope--;
+ decomp.code.AddLine("}}");
+ }
+
+ void operator()(VideoCommon::Shader::ASTIfElse& ast) {
+ decomp.code.AddLine("else {{");
+ decomp.code.scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ decomp.code.scope--;
+ decomp.code.AddLine("}}");
+ }
+
+ void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
+ UNREACHABLE();
+ }
+
+ void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
+ decomp.VisitBlock(ast.nodes);
+ }
+
+ void operator()(VideoCommon::Shader::ASTVarSet& ast) {
+ ExprDecompiler expr_parser{decomp};
+ std::visit(expr_parser, *ast.condition);
+ decomp.code.AddLine("{} = {};", GetFlowVariable(ast.index), expr_parser.GetResult());
+ }
+
+ void operator()(VideoCommon::Shader::ASTLabel& ast) {
+ decomp.code.AddLine("// Label_{}:", ast.index);
+ }
+
+ void operator()(VideoCommon::Shader::ASTGoto& ast) {
+ UNREACHABLE();
+ }
+
+ void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
+ ExprDecompiler expr_parser{decomp};
+ std::visit(expr_parser, *ast.condition);
+ decomp.code.AddLine("do {{");
+ decomp.code.scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ decomp.code.scope--;
+ decomp.code.AddLine("}} while({});", expr_parser.GetResult());
+ }
+
+ void operator()(VideoCommon::Shader::ASTReturn& ast) {
+ const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
+ if (!is_true) {
+ ExprDecompiler expr_parser{decomp};
+ std::visit(expr_parser, *ast.condition);
+ decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
+ decomp.code.scope++;
+ }
+ if (ast.kills) {
+ decomp.code.AddLine("discard;");
+ } else {
+ decomp.PreExit();
+ decomp.code.AddLine("return;");
+ }
+ if (!is_true) {
+ decomp.code.scope--;
+ decomp.code.AddLine("}}");
+ }
+ }
+
+ void operator()(VideoCommon::Shader::ASTBreak& ast) {
+ const bool is_true = VideoCommon::Shader::ExprIsTrue(ast.condition);
+ if (!is_true) {
+ ExprDecompiler expr_parser{decomp};
+ std::visit(expr_parser, *ast.condition);
+ decomp.code.AddLine("if ({}) {{", expr_parser.GetResult());
+ decomp.code.scope++;
+ }
+ decomp.code.AddLine("break;");
+ if (!is_true) {
+ decomp.code.scope--;
+ decomp.code.AddLine("}}");
+ }
+ }
+
+ void Visit(VideoCommon::Shader::ASTNode& node) {
+ std::visit(*this, *node->GetInnerData());
+ }
+
+private:
+ GLSLDecompiler& decomp;
+};
+
+void GLSLDecompiler::DecompileAST() {
+ const u32 num_flow_variables = ir.GetASTNumVariables();
+ for (u32 i = 0; i < num_flow_variables; i++) {
+ code.AddLine("bool {} = false;", GetFlowVariable(i));
+ }
+ ASTDecompiler decompiler{*this};
+ VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
+ decompiler.Visit(program);
+}
+
} // Anonymous namespace
std::string GetCommonDeclarations() {
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index 2ea02f5bf..e538dc001 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -90,7 +90,6 @@ struct ShaderEntries {
std::vector<ImageEntry> images;
std::vector<GlobalMemoryEntry> global_memory_entries;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
- bool shader_viewport_layer_array{};
std::size_t shader_length{};
};
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index f141c4e3b..74cc33476 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -112,14 +112,15 @@ std::optional<std::pair<std::vector<ShaderDiskCacheRaw>, std::vector<ShaderDiskC
ShaderDiskCacheOpenGL::LoadTransferable() {
// Skip games without title id
const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
- if (!Settings::values.use_disk_shader_cache || !has_title_id)
+ if (!Settings::values.use_disk_shader_cache || !has_title_id) {
return {};
- tried_to_load = true;
+ }
FileUtil::IOFile file(GetTransferablePath(), "rb");
if (!file.IsOpen()) {
LOG_INFO(Render_OpenGL, "No transferable shader cache found for game with title id={}",
GetTitleID());
+ is_usable = true;
return {};
}
@@ -135,6 +136,7 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing");
file.Close();
InvalidateTransferable();
+ is_usable = true;
return {};
}
if (version > NativeVersion) {
@@ -180,13 +182,15 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
}
}
- return {{raws, usages}};
+ is_usable = true;
+ return {{std::move(raws), std::move(usages)}};
}
std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
ShaderDiskCacheOpenGL::LoadPrecompiled() {
- if (!IsUsable())
+ if (!is_usable) {
return {};
+ }
FileUtil::IOFile file(GetPrecompiledPath(), "rb");
if (!file.IsOpen()) {
@@ -343,20 +347,17 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
u8 is_bindless{};
u8 is_written{};
u8 is_read{};
- u8 is_size_known{};
- u32 size{};
+ u8 is_atomic{};
if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
!LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
!LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) ||
- !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) {
+ !LoadObjectFromPrecompiled(is_atomic)) {
return {};
}
entry.entries.images.emplace_back(
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0,
- is_read != 0,
- is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size))
- : std::nullopt);
+ is_read != 0, is_atomic != 0);
}
u32 global_memory_count{};
@@ -382,12 +383,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
}
}
- bool shader_viewport_layer_array{};
- if (!LoadObjectFromPrecompiled(shader_viewport_layer_array)) {
- return {};
- }
- entry.entries.shader_viewport_layer_array = shader_viewport_layer_array;
-
u64 shader_length{};
if (!LoadObjectFromPrecompiled(shader_length)) {
return {};
@@ -435,14 +430,13 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
return false;
}
for (const auto& image : entries.images) {
- const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U;
if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
!SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
!SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
- !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) {
+ !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) {
return false;
}
}
@@ -464,10 +458,6 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
}
}
- if (!SaveObjectToPrecompiled(entries.shader_viewport_layer_array)) {
- return false;
- }
-
if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
return false;
}
@@ -493,8 +483,9 @@ void ShaderDiskCacheOpenGL::InvalidatePrecompiled() {
}
void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {
- if (!IsUsable())
+ if (!is_usable) {
return;
+ }
const u64 id = entry.GetUniqueIdentifier();
if (transferable.find(id) != transferable.end()) {
@@ -515,8 +506,9 @@ void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {
}
void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
- if (!IsUsable())
+ if (!is_usable) {
return;
+ }
const auto it = transferable.find(usage.unique_identifier);
ASSERT_MSG(it != transferable.end(), "Saving shader usage without storing raw previously");
@@ -542,8 +534,9 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code,
const GLShader::ShaderEntries& entries) {
- if (!IsUsable())
+ if (!is_usable) {
return;
+ }
if (precompiled_cache_virtual_file.GetSize() == 0) {
SavePrecompiledHeaderToVirtualPrecompiledCache();
@@ -557,8 +550,9 @@ void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::str
}
void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint program) {
- if (!IsUsable())
+ if (!is_usable) {
return;
+ }
GLint binary_length{};
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length);
@@ -579,10 +573,6 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
}
}
-bool ShaderDiskCacheOpenGL::IsUsable() const {
- return tried_to_load && Settings::values.use_disk_shader_cache;
-}
-
FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
if (!EnsureDirectories())
return {};
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index cc8bbd61e..9595bd71b 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -224,9 +224,6 @@ private:
bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,
const GLShader::ShaderEntries& entries);
- /// Returns if the cache can be used
- bool IsUsable() const;
-
/// Opens current game's transferable file and write it's header if it doesn't exist
FileUtil::IOFile AppendTransferableFile() const;
@@ -297,7 +294,7 @@ private:
std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
// The cache has been loaded at boot
- bool tried_to_load{};
+ bool is_usable{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 3a8d9e1da..b5a43e79e 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -11,12 +11,16 @@
namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D;
+using VideoCommon::Shader::CompileDepth;
+using VideoCommon::Shader::CompilerSettings;
using VideoCommon::Shader::ProgramCode;
using VideoCommon::Shader::ShaderIR;
static constexpr u32 PROGRAM_OFFSET = 10;
static constexpr u32 COMPUTE_OFFSET = 0;
+static constexpr CompilerSettings settings{CompileDepth::NoFlowStack, true};
+
ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
@@ -31,13 +35,14 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
)";
- const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB;
ProgramResult program = Decompile(device, program_ir, stage, "vertex");
out += program.first;
if (setup.IsDualProgram()) {
- const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b);
+ const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b,
+ settings);
ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b");
out += program_b.first;
}
@@ -80,7 +85,7 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
)";
- const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry");
out += program.first;
@@ -114,7 +119,8 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
};
)";
- const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a);
+
+ const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment");
out += program.first;
@@ -133,7 +139,7 @@ ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& set
std::string out = "// Shader Unique Id: CS" + id + "\n\n";
out += GetCommonDeclarations();
- const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a);
+ const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a, settings);
ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute");
out += program.first;
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index f7fbbb6e4..8bcd04221 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -19,6 +19,7 @@
#include "video_core/engines/shader_header.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
+#include "video_core/shader/node.h"
#include "video_core/shader/shader_ir.h"
namespace Vulkan::VKShader {
@@ -87,6 +88,9 @@ bool IsPrecise(Operation operand) {
} // namespace
+class ASTDecompiler;
+class ExprDecompiler;
+
class SPIRVDecompiler : public Sirit::Module {
public:
explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderStage stage)
@@ -96,27 +100,7 @@ public:
AddExtension("SPV_KHR_variable_pointers");
}
- void Decompile() {
- AllocateBindings();
- AllocateLabels();
-
- DeclareVertex();
- DeclareGeometry();
- DeclareFragment();
- DeclareRegisters();
- DeclarePredicates();
- DeclareLocalMemory();
- DeclareInternalFlags();
- DeclareInputAttributes();
- DeclareOutputAttributes();
- DeclareConstantBuffers();
- DeclareGlobalBuffers();
- DeclareSamplers();
-
- execute_function =
- Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
- Emit(OpLabel());
-
+ void DecompileBranchMode() {
const u32 first_address = ir.GetBasicBlocks().begin()->first;
const Id loop_label = OpLabel("loop");
const Id merge_label = OpLabel("merge");
@@ -173,6 +157,43 @@ public:
Emit(continue_label);
Emit(OpBranch(loop_label));
Emit(merge_label);
+ }
+
+ void DecompileAST();
+
+ void Decompile() {
+ const bool is_fully_decompiled = ir.IsDecompiled();
+ AllocateBindings();
+ if (!is_fully_decompiled) {
+ AllocateLabels();
+ }
+
+ DeclareVertex();
+ DeclareGeometry();
+ DeclareFragment();
+ DeclareRegisters();
+ DeclarePredicates();
+ if (is_fully_decompiled) {
+ DeclareFlowVariables();
+ }
+ DeclareLocalMemory();
+ DeclareInternalFlags();
+ DeclareInputAttributes();
+ DeclareOutputAttributes();
+ DeclareConstantBuffers();
+ DeclareGlobalBuffers();
+ DeclareSamplers();
+
+ execute_function =
+ Emit(OpFunction(t_void, spv::FunctionControlMask::Inline, TypeFunction(t_void)));
+ Emit(OpLabel());
+
+ if (is_fully_decompiled) {
+ DecompileAST();
+ } else {
+ DecompileBranchMode();
+ }
+
Emit(OpReturn());
Emit(OpFunctionEnd());
}
@@ -205,6 +226,9 @@ public:
}
private:
+ friend class ASTDecompiler;
+ friend class ExprDecompiler;
+
static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount);
void AllocateBindings() {
@@ -293,6 +317,14 @@ private:
}
}
+ void DeclareFlowVariables() {
+ for (u32 i = 0; i < ir.GetASTNumVariables(); i++) {
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ Name(id, fmt::format("flow_var_{}", static_cast<u32>(i)));
+ flow_variables.emplace(i, AddGlobalVariable(id));
+ }
+ }
+
void DeclareLocalMemory() {
if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) {
const auto element_count = static_cast<u32>(Common::AlignUp(local_memory_size, 4) / 4);
@@ -614,9 +646,15 @@ private:
Emit(OpBranchConditional(condition, true_label, skip_label));
Emit(true_label);
+ ++conditional_nest_count;
VisitBasicBlock(conditional->GetCode());
+ --conditional_nest_count;
- Emit(OpBranch(skip_label));
+ if (inside_branch == 0) {
+ Emit(OpBranch(skip_label));
+ } else {
+ inside_branch--;
+ }
Emit(skip_label);
return {};
@@ -939,22 +977,17 @@ private:
return {};
}
- Id ImageStore(Operation operation) {
- UNIMPLEMENTED();
- return {};
- }
-
- Id AtomicImageAdd(Operation operation) {
+ Id ImageLoad(Operation operation) {
UNIMPLEMENTED();
return {};
}
- Id AtomicImageMin(Operation operation) {
+ Id ImageStore(Operation operation) {
UNIMPLEMENTED();
return {};
}
- Id AtomicImageMax(Operation operation) {
+ Id AtomicImageAdd(Operation operation) {
UNIMPLEMENTED();
return {};
}
@@ -984,7 +1017,11 @@ private:
UNIMPLEMENTED_IF(!target);
Emit(OpStore(jmp_to, Constant(t_uint, target->GetValue())));
- BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ Emit(OpBranch(continue_label));
+ inside_branch = conditional_nest_count;
+ if (conditional_nest_count == 0) {
+ Emit(OpLabel());
+ }
return {};
}
@@ -992,7 +1029,11 @@ private:
const Id op_a = VisitOperand<Type::Uint>(operation, 0);
Emit(OpStore(jmp_to, op_a));
- BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ Emit(OpBranch(continue_label));
+ inside_branch = conditional_nest_count;
+ if (conditional_nest_count == 0) {
+ Emit(OpLabel());
+ }
return {};
}
@@ -1019,11 +1060,15 @@ private:
Emit(OpStore(flow_stack_top, previous));
Emit(OpStore(jmp_to, target));
- BranchingOp([&]() { Emit(OpBranch(continue_label)); });
+ Emit(OpBranch(continue_label));
+ inside_branch = conditional_nest_count;
+ if (conditional_nest_count == 0) {
+ Emit(OpLabel());
+ }
return {};
}
- Id Exit(Operation operation) {
+ Id PreExit() {
switch (stage) {
case ShaderStage::Vertex: {
// TODO(Rodrigo): We should use VK_EXT_depth_range_unrestricted instead, but it doesn't
@@ -1071,12 +1116,35 @@ private:
}
}
- BranchingOp([&]() { Emit(OpReturn()); });
+ return {};
+ }
+
+ Id Exit(Operation operation) {
+ PreExit();
+ inside_branch = conditional_nest_count;
+ if (conditional_nest_count > 0) {
+ Emit(OpReturn());
+ } else {
+ const Id dummy = OpLabel();
+ Emit(OpBranch(dummy));
+ Emit(dummy);
+ Emit(OpReturn());
+ Emit(OpLabel());
+ }
return {};
}
Id Discard(Operation operation) {
- BranchingOp([&]() { Emit(OpKill()); });
+ inside_branch = conditional_nest_count;
+ if (conditional_nest_count > 0) {
+ Emit(OpKill());
+ } else {
+ const Id dummy = OpLabel();
+ Emit(OpBranch(dummy));
+ Emit(dummy);
+ Emit(OpKill());
+ Emit(OpLabel());
+ }
return {};
}
@@ -1271,17 +1339,6 @@ private:
return {};
}
- void BranchingOp(std::function<void()> call) {
- const Id true_label = OpLabel();
- const Id skip_label = OpLabel();
- Emit(OpSelectionMerge(skip_label, spv::SelectionControlMask::Flatten));
- Emit(OpBranchConditional(v_true, true_label, skip_label, 1, 0));
- Emit(true_label);
- call();
-
- Emit(skip_label);
- }
-
std::tuple<Id, Id> CreateFlowStack() {
// TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely
// that shaders will use 20 nested SSYs and PBKs.
@@ -1440,10 +1497,9 @@ private:
&SPIRVDecompiler::TextureQueryLod,
&SPIRVDecompiler::TexelFetch,
+ &SPIRVDecompiler::ImageLoad,
&SPIRVDecompiler::ImageStore,
&SPIRVDecompiler::AtomicImageAdd,
- &SPIRVDecompiler::AtomicImageMin,
- &SPIRVDecompiler::AtomicImageMax,
&SPIRVDecompiler::AtomicImageAnd,
&SPIRVDecompiler::AtomicImageOr,
&SPIRVDecompiler::AtomicImageXor,
@@ -1488,6 +1544,8 @@ private:
const ShaderIR& ir;
const ShaderStage stage;
const Tegra::Shader::Header header;
+ u64 conditional_nest_count{};
+ u64 inside_branch{};
const Id t_void = Name(TypeVoid(), "void");
@@ -1550,6 +1608,7 @@ private:
Id per_vertex{};
std::map<u32, Id> registers;
std::map<Tegra::Shader::Pred, Id> predicates;
+ std::map<u32, Id> flow_variables;
Id local_memory{};
std::array<Id, INTERNAL_FLAGS_COUNT> internal_flags{};
std::map<Attribute::Index, Id> input_attributes;
@@ -1585,6 +1644,223 @@ private:
std::map<u32, Id> labels;
};
+class ExprDecompiler {
+public:
+ explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
+
+ Id operator()(VideoCommon::Shader::ExprAnd& expr) {
+ const Id type_def = decomp.GetTypeDefinition(Type::Bool);
+ const Id op1 = Visit(expr.operand1);
+ const Id op2 = Visit(expr.operand2);
+ return decomp.Emit(decomp.OpLogicalAnd(type_def, op1, op2));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprOr& expr) {
+ const Id type_def = decomp.GetTypeDefinition(Type::Bool);
+ const Id op1 = Visit(expr.operand1);
+ const Id op2 = Visit(expr.operand2);
+ return decomp.Emit(decomp.OpLogicalOr(type_def, op1, op2));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprNot& expr) {
+ const Id type_def = decomp.GetTypeDefinition(Type::Bool);
+ const Id op1 = Visit(expr.operand1);
+ return decomp.Emit(decomp.OpLogicalNot(type_def, op1));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprPredicate& expr) {
+ const auto pred = static_cast<Tegra::Shader::Pred>(expr.predicate);
+ return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.predicates.at(pred)));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprCondCode& expr) {
+ const Node cc = decomp.ir.GetConditionCode(expr.cc);
+ Id target;
+
+ if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
+ const auto index = pred->GetIndex();
+ switch (index) {
+ case Tegra::Shader::Pred::NeverExecute:
+ target = decomp.v_false;
+ case Tegra::Shader::Pred::UnusedIndex:
+ target = decomp.v_true;
+ default:
+ target = decomp.predicates.at(index);
+ }
+ } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
+ target = decomp.internal_flags.at(static_cast<u32>(flag->GetFlag()));
+ }
+ return decomp.Emit(decomp.OpLoad(decomp.t_bool, target));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprVar& expr) {
+ return decomp.Emit(decomp.OpLoad(decomp.t_bool, decomp.flow_variables.at(expr.var_index)));
+ }
+
+ Id operator()(VideoCommon::Shader::ExprBoolean& expr) {
+ return expr.value ? decomp.v_true : decomp.v_false;
+ }
+
+ Id Visit(VideoCommon::Shader::Expr& node) {
+ return std::visit(*this, *node);
+ }
+
+private:
+ SPIRVDecompiler& decomp;
+};
+
+class ASTDecompiler {
+public:
+ explicit ASTDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
+
+ void operator()(VideoCommon::Shader::ASTProgram& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(VideoCommon::Shader::ASTIfThen& ast) {
+ ExprDecompiler expr_parser{decomp};
+ const Id condition = expr_parser.Visit(ast.condition);
+ const Id then_label = decomp.OpLabel();
+ const Id endif_label = decomp.OpLabel();
+ decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
+ decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
+ decomp.Emit(then_label);
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ decomp.Emit(decomp.OpBranch(endif_label));
+ decomp.Emit(endif_label);
+ }
+
+ void operator()(VideoCommon::Shader::ASTIfElse& ast) {
+ UNREACHABLE();
+ }
+
+ void operator()(VideoCommon::Shader::ASTBlockEncoded& ast) {
+ UNREACHABLE();
+ }
+
+ void operator()(VideoCommon::Shader::ASTBlockDecoded& ast) {
+ decomp.VisitBasicBlock(ast.nodes);
+ }
+
+ void operator()(VideoCommon::Shader::ASTVarSet& ast) {
+ ExprDecompiler expr_parser{decomp};
+ const Id condition = expr_parser.Visit(ast.condition);
+ decomp.Emit(decomp.OpStore(decomp.flow_variables.at(ast.index), condition));
+ }
+
+ void operator()(VideoCommon::Shader::ASTLabel& ast) {
+ // Do nothing
+ }
+
+ void operator()(VideoCommon::Shader::ASTGoto& ast) {
+ UNREACHABLE();
+ }
+
+ void operator()(VideoCommon::Shader::ASTDoWhile& ast) {
+ const Id loop_label = decomp.OpLabel();
+ const Id endloop_label = decomp.OpLabel();
+ const Id loop_start_block = decomp.OpLabel();
+ const Id loop_end_block = decomp.OpLabel();
+ current_loop_exit = endloop_label;
+ decomp.Emit(decomp.OpBranch(loop_label));
+ decomp.Emit(loop_label);
+ decomp.Emit(
+ decomp.OpLoopMerge(endloop_label, loop_end_block, spv::LoopControlMask::MaskNone));
+ decomp.Emit(decomp.OpBranch(loop_start_block));
+ decomp.Emit(loop_start_block);
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ ExprDecompiler expr_parser{decomp};
+ const Id condition = expr_parser.Visit(ast.condition);
+ decomp.Emit(decomp.OpBranchConditional(condition, loop_label, endloop_label));
+ decomp.Emit(endloop_label);
+ }
+
+ void operator()(VideoCommon::Shader::ASTReturn& ast) {
+ if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
+ ExprDecompiler expr_parser{decomp};
+ const Id condition = expr_parser.Visit(ast.condition);
+ const Id then_label = decomp.OpLabel();
+ const Id endif_label = decomp.OpLabel();
+ decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
+ decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
+ decomp.Emit(then_label);
+ if (ast.kills) {
+ decomp.Emit(decomp.OpKill());
+ } else {
+ decomp.PreExit();
+ decomp.Emit(decomp.OpReturn());
+ }
+ decomp.Emit(endif_label);
+ } else {
+ const Id next_block = decomp.OpLabel();
+ decomp.Emit(decomp.OpBranch(next_block));
+ decomp.Emit(next_block);
+ if (ast.kills) {
+ decomp.Emit(decomp.OpKill());
+ } else {
+ decomp.PreExit();
+ decomp.Emit(decomp.OpReturn());
+ }
+ decomp.Emit(decomp.OpLabel());
+ }
+ }
+
+ void operator()(VideoCommon::Shader::ASTBreak& ast) {
+ if (!VideoCommon::Shader::ExprIsTrue(ast.condition)) {
+ ExprDecompiler expr_parser{decomp};
+ const Id condition = expr_parser.Visit(ast.condition);
+ const Id then_label = decomp.OpLabel();
+ const Id endif_label = decomp.OpLabel();
+ decomp.Emit(decomp.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone));
+ decomp.Emit(decomp.OpBranchConditional(condition, then_label, endif_label));
+ decomp.Emit(then_label);
+ decomp.Emit(decomp.OpBranch(current_loop_exit));
+ decomp.Emit(endif_label);
+ } else {
+ const Id next_block = decomp.OpLabel();
+ decomp.Emit(decomp.OpBranch(next_block));
+ decomp.Emit(next_block);
+ decomp.Emit(decomp.OpBranch(current_loop_exit));
+ decomp.Emit(decomp.OpLabel());
+ }
+ }
+
+ void Visit(VideoCommon::Shader::ASTNode& node) {
+ std::visit(*this, *node->GetInnerData());
+ }
+
+private:
+ SPIRVDecompiler& decomp;
+ Id current_loop_exit{};
+};
+
+void SPIRVDecompiler::DecompileAST() {
+ const u32 num_flow_variables = ir.GetASTNumVariables();
+ for (u32 i = 0; i < num_flow_variables; i++) {
+ const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
+ Name(id, fmt::format("flow_var_{}", i));
+ flow_variables.emplace(i, AddGlobalVariable(id));
+ }
+ ASTDecompiler decompiler{*this};
+ VideoCommon::Shader::ASTNode program = ir.GetASTProgram();
+ decompiler.Visit(program);
+ const Id next_block = OpLabel();
+ Emit(OpBranch(next_block));
+ Emit(next_block);
+}
+
DecompilerResult Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
Maxwell::ShaderStage stage) {
auto decompiler = std::make_unique<SPIRVDecompiler>(device, ir, stage);
diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp
new file mode 100644
index 000000000..436d45f4b
--- /dev/null
+++ b/src/video_core/shader/ast.cpp
@@ -0,0 +1,738 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/shader/ast.h"
+#include "video_core/shader/expr.h"
+
+namespace VideoCommon::Shader {
+
+ASTZipper::ASTZipper() = default;
+
+void ASTZipper::Init(const ASTNode new_first, const ASTNode parent) {
+ ASSERT(new_first->manager == nullptr);
+ first = new_first;
+ last = new_first;
+
+ ASTNode current = first;
+ while (current) {
+ current->manager = this;
+ current->parent = parent;
+ last = current;
+ current = current->next;
+ }
+}
+
+void ASTZipper::PushBack(const ASTNode new_node) {
+ ASSERT(new_node->manager == nullptr);
+ new_node->previous = last;
+ if (last) {
+ last->next = new_node;
+ }
+ new_node->next.reset();
+ last = new_node;
+ if (!first) {
+ first = new_node;
+ }
+ new_node->manager = this;
+}
+
+void ASTZipper::PushFront(const ASTNode new_node) {
+ ASSERT(new_node->manager == nullptr);
+ new_node->previous.reset();
+ new_node->next = first;
+ if (first) {
+ first->previous = new_node;
+ }
+ if (last == first) {
+ last = new_node;
+ }
+ first = new_node;
+ new_node->manager = this;
+}
+
+void ASTZipper::InsertAfter(const ASTNode new_node, const ASTNode at_node) {
+ ASSERT(new_node->manager == nullptr);
+ if (!at_node) {
+ PushFront(new_node);
+ return;
+ }
+ const ASTNode next = at_node->next;
+ if (next) {
+ next->previous = new_node;
+ }
+ new_node->previous = at_node;
+ if (at_node == last) {
+ last = new_node;
+ }
+ new_node->next = next;
+ at_node->next = new_node;
+ new_node->manager = this;
+}
+
+void ASTZipper::InsertBefore(const ASTNode new_node, const ASTNode at_node) {
+ ASSERT(new_node->manager == nullptr);
+ if (!at_node) {
+ PushBack(new_node);
+ return;
+ }
+ const ASTNode previous = at_node->previous;
+ if (previous) {
+ previous->next = new_node;
+ }
+ new_node->next = at_node;
+ if (at_node == first) {
+ first = new_node;
+ }
+ new_node->previous = previous;
+ at_node->previous = new_node;
+ new_node->manager = this;
+}
+
+void ASTZipper::DetachTail(ASTNode node) {
+ ASSERT(node->manager == this);
+ if (node == first) {
+ first.reset();
+ last.reset();
+ return;
+ }
+
+ last = node->previous;
+ last->next.reset();
+ node->previous.reset();
+
+ ASTNode current = std::move(node);
+ while (current) {
+ current->manager = nullptr;
+ current->parent.reset();
+ current = current->next;
+ }
+}
+
+void ASTZipper::DetachSegment(const ASTNode start, const ASTNode end) {
+ ASSERT(start->manager == this && end->manager == this);
+ if (start == end) {
+ DetachSingle(start);
+ return;
+ }
+ const ASTNode prev = start->previous;
+ const ASTNode post = end->next;
+ if (!prev) {
+ first = post;
+ } else {
+ prev->next = post;
+ }
+ if (!post) {
+ last = prev;
+ } else {
+ post->previous = prev;
+ }
+ start->previous.reset();
+ end->next.reset();
+ ASTNode current = start;
+ bool found = false;
+ while (current) {
+ current->manager = nullptr;
+ current->parent.reset();
+ found |= current == end;
+ current = current->next;
+ }
+ ASSERT(found);
+}
+
+void ASTZipper::DetachSingle(const ASTNode node) {
+ ASSERT(node->manager == this);
+ const ASTNode prev = node->previous;
+ const ASTNode post = node->next;
+ node->previous.reset();
+ node->next.reset();
+ if (!prev) {
+ first = post;
+ } else {
+ prev->next = post;
+ }
+ if (!post) {
+ last = prev;
+ } else {
+ post->previous = prev;
+ }
+
+ node->manager = nullptr;
+ node->parent.reset();
+}
+
+void ASTZipper::Remove(const ASTNode node) {
+ ASSERT(node->manager == this);
+ const ASTNode next = node->next;
+ const ASTNode previous = node->previous;
+ if (previous) {
+ previous->next = next;
+ }
+ if (next) {
+ next->previous = previous;
+ }
+ node->parent.reset();
+ node->manager = nullptr;
+ if (node == last) {
+ last = previous;
+ }
+ if (node == first) {
+ first = next;
+ }
+}
+
+class ExprPrinter final {
+public:
+ void operator()(const ExprAnd& expr) {
+ inner += "( ";
+ std::visit(*this, *expr.operand1);
+ inner += " && ";
+ std::visit(*this, *expr.operand2);
+ inner += ')';
+ }
+
+ void operator()(const ExprOr& expr) {
+ inner += "( ";
+ std::visit(*this, *expr.operand1);
+ inner += " || ";
+ std::visit(*this, *expr.operand2);
+ inner += ')';
+ }
+
+ void operator()(const ExprNot& expr) {
+ inner += "!";
+ std::visit(*this, *expr.operand1);
+ }
+
+ void operator()(const ExprPredicate& expr) {
+ inner += "P" + std::to_string(expr.predicate);
+ }
+
+ void operator()(const ExprCondCode& expr) {
+ u32 cc = static_cast<u32>(expr.cc);
+ inner += "CC" + std::to_string(cc);
+ }
+
+ void operator()(const ExprVar& expr) {
+ inner += "V" + std::to_string(expr.var_index);
+ }
+
+ void operator()(const ExprBoolean& expr) {
+ inner += expr.value ? "true" : "false";
+ }
+
+ const std::string& GetResult() const {
+ return inner;
+ }
+
+ std::string inner{};
+};
+
+class ASTPrinter {
+public:
+ void operator()(const ASTProgram& ast) {
+ scope++;
+ inner += "program {\n";
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ inner += "}\n";
+ scope--;
+ }
+
+ void operator()(const ASTIfThen& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner += Ident() + "if (" + expr_parser.GetResult() + ") {\n";
+ scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ scope--;
+ inner += Ident() + "}\n";
+ }
+
+ void operator()(const ASTIfElse& ast) {
+ inner += Ident() + "else {\n";
+ scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ scope--;
+ inner += Ident() + "}\n";
+ }
+
+ void operator()(const ASTBlockEncoded& ast) {
+ inner += Ident() + "Block(" + std::to_string(ast.start) + ", " + std::to_string(ast.end) +
+ ");\n";
+ }
+
+ void operator()(const ASTBlockDecoded& ast) {
+ inner += Ident() + "Block;\n";
+ }
+
+ void operator()(const ASTVarSet& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner +=
+ Ident() + "V" + std::to_string(ast.index) + " := " + expr_parser.GetResult() + ";\n";
+ }
+
+ void operator()(const ASTLabel& ast) {
+ inner += "Label_" + std::to_string(ast.index) + ":\n";
+ }
+
+ void operator()(const ASTGoto& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner += Ident() + "(" + expr_parser.GetResult() + ") -> goto Label_" +
+ std::to_string(ast.label) + ";\n";
+ }
+
+ void operator()(const ASTDoWhile& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner += Ident() + "do {\n";
+ scope++;
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ scope--;
+ inner += Ident() + "} while (" + expr_parser.GetResult() + ");\n";
+ }
+
+ void operator()(const ASTReturn& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner += Ident() + "(" + expr_parser.GetResult() + ") -> " +
+ (ast.kills ? "discard" : "exit") + ";\n";
+ }
+
+ void operator()(const ASTBreak& ast) {
+ ExprPrinter expr_parser{};
+ std::visit(expr_parser, *ast.condition);
+ inner += Ident() + "(" + expr_parser.GetResult() + ") -> break;\n";
+ }
+
+ std::string& Ident() {
+ if (memo_scope == scope) {
+ return tabs_memo;
+ }
+ tabs_memo = tabs.substr(0, scope * 2);
+ memo_scope = scope;
+ return tabs_memo;
+ }
+
+ void Visit(ASTNode& node) {
+ std::visit(*this, *node->GetInnerData());
+ }
+
+ const std::string& GetResult() const {
+ return inner;
+ }
+
+private:
+ std::string inner{};
+ u32 scope{};
+
+ std::string tabs_memo{};
+ u32 memo_scope{};
+
+ static constexpr std::string_view tabs{" "};
+};
+
+std::string ASTManager::Print() {
+ ASTPrinter printer{};
+ printer.Visit(main_node);
+ return printer.GetResult();
+}
+
+ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation)
+ : full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {};
+
+ASTManager::~ASTManager() {
+ Clear();
+}
+
+void ASTManager::Init() {
+ main_node = ASTBase::Make<ASTProgram>(ASTNode{});
+ program = std::get_if<ASTProgram>(main_node->GetInnerData());
+ false_condition = MakeExpr<ExprBoolean>(false);
+}
+
+void ASTManager::DeclareLabel(u32 address) {
+ const auto pair = labels_map.emplace(address, labels_count);
+ if (pair.second) {
+ labels_count++;
+ labels.resize(labels_count);
+ }
+}
+
+void ASTManager::InsertLabel(u32 address) {
+ const u32 index = labels_map[address];
+ const ASTNode label = ASTBase::Make<ASTLabel>(main_node, index);
+ labels[index] = label;
+ program->nodes.PushBack(label);
+}
+
+void ASTManager::InsertGoto(Expr condition, u32 address) {
+ const u32 index = labels_map[address];
+ const ASTNode goto_node = ASTBase::Make<ASTGoto>(main_node, std::move(condition), index);
+ gotos.push_back(goto_node);
+ program->nodes.PushBack(goto_node);
+}
+
+void ASTManager::InsertBlock(u32 start_address, u32 end_address) {
+ ASTNode block = ASTBase::Make<ASTBlockEncoded>(main_node, start_address, end_address);
+ program->nodes.PushBack(std::move(block));
+}
+
+void ASTManager::InsertReturn(Expr condition, bool kills) {
+ ASTNode node = ASTBase::Make<ASTReturn>(main_node, std::move(condition), kills);
+ program->nodes.PushBack(std::move(node));
+}
+
+// The decompile algorithm is based on
+// "Taming control flow: A structured approach to eliminating goto statements"
+// by AM Erosa, LJ Hendren 1994. In general, the idea is to get gotos to be
+// on the same structured level as the label which they jump to. This is done,
+// through outward/inward movements and lifting. Once they are at the same
+// level, you can enclose them in an "if" structure or a "do-while" structure.
+void ASTManager::Decompile() {
+ auto it = gotos.begin();
+ while (it != gotos.end()) {
+ const ASTNode goto_node = *it;
+ const auto label_index = goto_node->GetGotoLabel();
+ if (!label_index) {
+ return;
+ }
+ const ASTNode label = labels[*label_index];
+ if (!full_decompile) {
+ // We only decompile backward jumps
+ if (!IsBackwardsJump(goto_node, label)) {
+ it++;
+ continue;
+ }
+ }
+ if (IndirectlyRelated(goto_node, label)) {
+ while (!DirectlyRelated(goto_node, label)) {
+ MoveOutward(goto_node);
+ }
+ }
+ if (DirectlyRelated(goto_node, label)) {
+ u32 goto_level = goto_node->GetLevel();
+ const u32 label_level = label->GetLevel();
+ while (label_level < goto_level) {
+ MoveOutward(goto_node);
+ goto_level--;
+ }
+ // TODO(Blinkhawk): Implement Lifting and Inward Movements
+ }
+ if (label->GetParent() == goto_node->GetParent()) {
+ bool is_loop = false;
+ ASTNode current = goto_node->GetPrevious();
+ while (current) {
+ if (current == label) {
+ is_loop = true;
+ break;
+ }
+ current = current->GetPrevious();
+ }
+
+ if (is_loop) {
+ EncloseDoWhile(goto_node, label);
+ } else {
+ EncloseIfThen(goto_node, label);
+ }
+ it = gotos.erase(it);
+ continue;
+ }
+ it++;
+ }
+ if (full_decompile) {
+ for (const ASTNode& label : labels) {
+ auto& manager = label->GetManager();
+ manager.Remove(label);
+ }
+ labels.clear();
+ } else {
+ auto label_it = labels.begin();
+ while (label_it != labels.end()) {
+ bool can_remove = true;
+ ASTNode label = *label_it;
+ for (const ASTNode& goto_node : gotos) {
+ const auto label_index = goto_node->GetGotoLabel();
+ if (!label_index) {
+ return;
+ }
+ ASTNode& glabel = labels[*label_index];
+ if (glabel == label) {
+ can_remove = false;
+ break;
+ }
+ }
+ if (can_remove) {
+ label->MarkLabelUnused();
+ }
+ }
+ }
+}
+
+bool ASTManager::IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const {
+ u32 goto_level = goto_node->GetLevel();
+ u32 label_level = label_node->GetLevel();
+ while (goto_level > label_level) {
+ goto_level--;
+ goto_node = goto_node->GetParent();
+ }
+ while (label_level > goto_level) {
+ label_level--;
+ label_node = label_node->GetParent();
+ }
+ while (goto_node->GetParent() != label_node->GetParent()) {
+ goto_node = goto_node->GetParent();
+ label_node = label_node->GetParent();
+ }
+ ASTNode current = goto_node->GetPrevious();
+ while (current) {
+ if (current == label_node) {
+ return true;
+ }
+ current = current->GetPrevious();
+ }
+ return false;
+}
+
+bool ASTManager::IndirectlyRelated(const ASTNode& first, const ASTNode& second) const {
+ return !(first->GetParent() == second->GetParent() || DirectlyRelated(first, second));
+}
+
+bool ASTManager::DirectlyRelated(const ASTNode& first, const ASTNode& second) const {
+ if (first->GetParent() == second->GetParent()) {
+ return false;
+ }
+ const u32 first_level = first->GetLevel();
+ const u32 second_level = second->GetLevel();
+ u32 min_level;
+ u32 max_level;
+ ASTNode max;
+ ASTNode min;
+ if (first_level > second_level) {
+ min_level = second_level;
+ min = second;
+ max_level = first_level;
+ max = first;
+ } else {
+ min_level = first_level;
+ min = first;
+ max_level = second_level;
+ max = second;
+ }
+
+ while (max_level > min_level) {
+ max_level--;
+ max = max->GetParent();
+ }
+
+ return min->GetParent() == max->GetParent();
+}
+
+void ASTManager::ShowCurrentState(std::string_view state) {
+ LOG_CRITICAL(HW_GPU, "\nState {}:\n\n{}\n", state, Print());
+ SanityCheck();
+}
+
+void ASTManager::SanityCheck() {
+ for (auto& label : labels) {
+ if (!label->GetParent()) {
+ LOG_CRITICAL(HW_GPU, "Sanity Check Failed");
+ }
+ }
+}
+
+void ASTManager::EncloseDoWhile(ASTNode goto_node, ASTNode label) {
+ ASTZipper& zipper = goto_node->GetManager();
+ const ASTNode loop_start = label->GetNext();
+ if (loop_start == goto_node) {
+ zipper.Remove(goto_node);
+ return;
+ }
+ const ASTNode parent = label->GetParent();
+ const Expr condition = goto_node->GetGotoCondition();
+ zipper.DetachSegment(loop_start, goto_node);
+ const ASTNode do_while_node = ASTBase::Make<ASTDoWhile>(parent, condition);
+ ASTZipper* sub_zipper = do_while_node->GetSubNodes();
+ sub_zipper->Init(loop_start, do_while_node);
+ zipper.InsertAfter(do_while_node, label);
+ sub_zipper->Remove(goto_node);
+}
+
+void ASTManager::EncloseIfThen(ASTNode goto_node, ASTNode label) {
+ ASTZipper& zipper = goto_node->GetManager();
+ const ASTNode if_end = label->GetPrevious();
+ if (if_end == goto_node) {
+ zipper.Remove(goto_node);
+ return;
+ }
+ const ASTNode prev = goto_node->GetPrevious();
+ const Expr condition = goto_node->GetGotoCondition();
+ bool do_else = false;
+ if (!disable_else_derivation && prev->IsIfThen()) {
+ const Expr if_condition = prev->GetIfCondition();
+ do_else = ExprAreEqual(if_condition, condition);
+ }
+ const ASTNode parent = label->GetParent();
+ zipper.DetachSegment(goto_node, if_end);
+ ASTNode if_node;
+ if (do_else) {
+ if_node = ASTBase::Make<ASTIfElse>(parent);
+ } else {
+ Expr neg_condition = MakeExprNot(condition);
+ if_node = ASTBase::Make<ASTIfThen>(parent, neg_condition);
+ }
+ ASTZipper* sub_zipper = if_node->GetSubNodes();
+ sub_zipper->Init(goto_node, if_node);
+ zipper.InsertAfter(if_node, prev);
+ sub_zipper->Remove(goto_node);
+}
+
+void ASTManager::MoveOutward(ASTNode goto_node) {
+ ASTZipper& zipper = goto_node->GetManager();
+ const ASTNode parent = goto_node->GetParent();
+ ASTZipper& zipper2 = parent->GetManager();
+ const ASTNode grandpa = parent->GetParent();
+ const bool is_loop = parent->IsLoop();
+ const bool is_else = parent->IsIfElse();
+ const bool is_if = parent->IsIfThen();
+
+ const ASTNode prev = goto_node->GetPrevious();
+ const ASTNode post = goto_node->GetNext();
+
+ const Expr condition = goto_node->GetGotoCondition();
+ zipper.DetachSingle(goto_node);
+ if (is_loop) {
+ const u32 var_index = NewVariable();
+ const Expr var_condition = MakeExpr<ExprVar>(var_index);
+ const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
+ const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
+ zipper2.InsertBefore(var_node_init, parent);
+ zipper.InsertAfter(var_node, prev);
+ goto_node->SetGotoCondition(var_condition);
+ const ASTNode break_node = ASTBase::Make<ASTBreak>(parent, var_condition);
+ zipper.InsertAfter(break_node, var_node);
+ } else if (is_if || is_else) {
+ const u32 var_index = NewVariable();
+ const Expr var_condition = MakeExpr<ExprVar>(var_index);
+ const ASTNode var_node = ASTBase::Make<ASTVarSet>(parent, var_index, condition);
+ const ASTNode var_node_init = ASTBase::Make<ASTVarSet>(parent, var_index, false_condition);
+ if (is_if) {
+ zipper2.InsertBefore(var_node_init, parent);
+ } else {
+ zipper2.InsertBefore(var_node_init, parent->GetPrevious());
+ }
+ zipper.InsertAfter(var_node, prev);
+ goto_node->SetGotoCondition(var_condition);
+ if (post) {
+ zipper.DetachTail(post);
+ const ASTNode if_node = ASTBase::Make<ASTIfThen>(parent, MakeExprNot(var_condition));
+ ASTZipper* sub_zipper = if_node->GetSubNodes();
+ sub_zipper->Init(post, if_node);
+ zipper.InsertAfter(if_node, var_node);
+ }
+ } else {
+ UNREACHABLE();
+ }
+ const ASTNode next = parent->GetNext();
+ if (is_if && next && next->IsIfElse()) {
+ zipper2.InsertAfter(goto_node, next);
+ goto_node->SetParent(grandpa);
+ return;
+ }
+ zipper2.InsertAfter(goto_node, parent);
+ goto_node->SetParent(grandpa);
+}
+
+class ASTClearer {
+public:
+ ASTClearer() = default;
+
+ void operator()(const ASTProgram& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(const ASTIfThen& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(const ASTIfElse& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()([[maybe_unused]] const ASTBlockEncoded& ast) {}
+
+ void operator()(ASTBlockDecoded& ast) {
+ ast.nodes.clear();
+ }
+
+ void operator()([[maybe_unused]] const ASTVarSet& ast) {}
+
+ void operator()([[maybe_unused]] const ASTLabel& ast) {}
+
+ void operator()([[maybe_unused]] const ASTGoto& ast) {}
+
+ void operator()(const ASTDoWhile& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()([[maybe_unused]] const ASTReturn& ast) {}
+
+ void operator()([[maybe_unused]] const ASTBreak& ast) {}
+
+ void Visit(const ASTNode& node) {
+ std::visit(*this, *node->GetInnerData());
+ node->Clear();
+ }
+};
+
+void ASTManager::Clear() {
+ if (!main_node) {
+ return;
+ }
+ ASTClearer clearer{};
+ clearer.Visit(main_node);
+ main_node.reset();
+ program = nullptr;
+ labels_map.clear();
+ labels.clear();
+ gotos.clear();
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
new file mode 100644
index 000000000..d7bf11821
--- /dev/null
+++ b/src/video_core/shader/ast.h
@@ -0,0 +1,400 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "video_core/shader/expr.h"
+#include "video_core/shader/node.h"
+
+namespace VideoCommon::Shader {
+
+class ASTBase;
+class ASTBlockDecoded;
+class ASTBlockEncoded;
+class ASTBreak;
+class ASTDoWhile;
+class ASTGoto;
+class ASTIfElse;
+class ASTIfThen;
+class ASTLabel;
+class ASTProgram;
+class ASTReturn;
+class ASTVarSet;
+
+using ASTData = std::variant<ASTProgram, ASTIfThen, ASTIfElse, ASTBlockEncoded, ASTBlockDecoded,
+ ASTVarSet, ASTGoto, ASTLabel, ASTDoWhile, ASTReturn, ASTBreak>;
+
+using ASTNode = std::shared_ptr<ASTBase>;
+
+enum class ASTZipperType : u32 {
+ Program,
+ IfThen,
+ IfElse,
+ Loop,
+};
+
+class ASTZipper final {
+public:
+ explicit ASTZipper();
+
+ void Init(ASTNode first, ASTNode parent);
+
+ ASTNode GetFirst() const {
+ return first;
+ }
+
+ ASTNode GetLast() const {
+ return last;
+ }
+
+ void PushBack(ASTNode new_node);
+ void PushFront(ASTNode new_node);
+ void InsertAfter(ASTNode new_node, ASTNode at_node);
+ void InsertBefore(ASTNode new_node, ASTNode at_node);
+ void DetachTail(ASTNode node);
+ void DetachSingle(ASTNode node);
+ void DetachSegment(ASTNode start, ASTNode end);
+ void Remove(ASTNode node);
+
+ ASTNode first{};
+ ASTNode last{};
+};
+
+class ASTProgram {
+public:
+ ASTZipper nodes{};
+};
+
+class ASTIfThen {
+public:
+ explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {}
+ Expr condition;
+ ASTZipper nodes{};
+};
+
+class ASTIfElse {
+public:
+ ASTZipper nodes{};
+};
+
+class ASTBlockEncoded {
+public:
+ explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
+ u32 start;
+ u32 end;
+};
+
+class ASTBlockDecoded {
+public:
+ explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {}
+ NodeBlock nodes;
+};
+
+class ASTVarSet {
+public:
+ explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {}
+ u32 index;
+ Expr condition;
+};
+
+class ASTLabel {
+public:
+ explicit ASTLabel(u32 index) : index{index} {}
+ u32 index;
+ bool unused{};
+};
+
+class ASTGoto {
+public:
+ explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {}
+ Expr condition;
+ u32 label;
+};
+
+class ASTDoWhile {
+public:
+ explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {}
+ Expr condition;
+ ASTZipper nodes{};
+};
+
+class ASTReturn {
+public:
+ explicit ASTReturn(Expr condition, bool kills)
+ : condition{std::move(condition)}, kills{kills} {}
+ Expr condition;
+ bool kills;
+};
+
+class ASTBreak {
+public:
+ explicit ASTBreak(Expr condition) : condition{std::move(condition)} {}
+ Expr condition;
+};
+
+class ASTBase {
+public:
+ explicit ASTBase(ASTNode parent, ASTData data)
+ : data{std::move(data)}, parent{std::move(parent)} {}
+
+ template <class U, class... Args>
+ static ASTNode Make(ASTNode parent, Args&&... args) {
+ return std::make_shared<ASTBase>(std::move(parent),
+ ASTData(U(std::forward<Args>(args)...)));
+ }
+
+ void SetParent(ASTNode new_parent) {
+ parent = std::move(new_parent);
+ }
+
+ ASTNode& GetParent() {
+ return parent;
+ }
+
+ const ASTNode& GetParent() const {
+ return parent;
+ }
+
+ u32 GetLevel() const {
+ u32 level = 0;
+ auto next_parent = parent;
+ while (next_parent) {
+ next_parent = next_parent->GetParent();
+ level++;
+ }
+ return level;
+ }
+
+ ASTData* GetInnerData() {
+ return &data;
+ }
+
+ const ASTData* GetInnerData() const {
+ return &data;
+ }
+
+ ASTNode GetNext() const {
+ return next;
+ }
+
+ ASTNode GetPrevious() const {
+ return previous;
+ }
+
+ ASTZipper& GetManager() {
+ return *manager;
+ }
+
+ const ASTZipper& GetManager() const {
+ return *manager;
+ }
+
+ std::optional<u32> GetGotoLabel() const {
+ auto inner = std::get_if<ASTGoto>(&data);
+ if (inner) {
+ return {inner->label};
+ }
+ return {};
+ }
+
+ Expr GetGotoCondition() const {
+ auto inner = std::get_if<ASTGoto>(&data);
+ if (inner) {
+ return inner->condition;
+ }
+ return nullptr;
+ }
+
+ void MarkLabelUnused() {
+ auto inner = std::get_if<ASTLabel>(&data);
+ if (inner) {
+ inner->unused = true;
+ }
+ }
+
+ bool IsLabelUnused() const {
+ auto inner = std::get_if<ASTLabel>(&data);
+ if (inner) {
+ return inner->unused;
+ }
+ return true;
+ }
+
+ std::optional<u32> GetLabelIndex() const {
+ auto inner = std::get_if<ASTLabel>(&data);
+ if (inner) {
+ return {inner->index};
+ }
+ return {};
+ }
+
+ Expr GetIfCondition() const {
+ auto inner = std::get_if<ASTIfThen>(&data);
+ if (inner) {
+ return inner->condition;
+ }
+ return nullptr;
+ }
+
+ void SetGotoCondition(Expr new_condition) {
+ auto inner = std::get_if<ASTGoto>(&data);
+ if (inner) {
+ inner->condition = std::move(new_condition);
+ }
+ }
+
+ bool IsIfThen() const {
+ return std::holds_alternative<ASTIfThen>(data);
+ }
+
+ bool IsIfElse() const {
+ return std::holds_alternative<ASTIfElse>(data);
+ }
+
+ bool IsBlockEncoded() const {
+ return std::holds_alternative<ASTBlockEncoded>(data);
+ }
+
+ void TransformBlockEncoded(NodeBlock&& nodes) {
+ data = ASTBlockDecoded(std::move(nodes));
+ }
+
+ bool IsLoop() const {
+ return std::holds_alternative<ASTDoWhile>(data);
+ }
+
+ ASTZipper* GetSubNodes() {
+ if (std::holds_alternative<ASTProgram>(data)) {
+ return &std::get_if<ASTProgram>(&data)->nodes;
+ }
+ if (std::holds_alternative<ASTIfThen>(data)) {
+ return &std::get_if<ASTIfThen>(&data)->nodes;
+ }
+ if (std::holds_alternative<ASTIfElse>(data)) {
+ return &std::get_if<ASTIfElse>(&data)->nodes;
+ }
+ if (std::holds_alternative<ASTDoWhile>(data)) {
+ return &std::get_if<ASTDoWhile>(&data)->nodes;
+ }
+ return nullptr;
+ }
+
+ void Clear() {
+ next.reset();
+ previous.reset();
+ parent.reset();
+ manager = nullptr;
+ }
+
+private:
+ friend class ASTZipper;
+
+ ASTData data;
+ ASTNode parent{};
+ ASTNode next{};
+ ASTNode previous{};
+ ASTZipper* manager{};
+};
+
+class ASTManager final {
+public:
+ ASTManager(bool full_decompile, bool disable_else_derivation);
+ ~ASTManager();
+
+ ASTManager(const ASTManager& o) = delete;
+ ASTManager& operator=(const ASTManager& other) = delete;
+
+ ASTManager(ASTManager&& other) noexcept = default;
+ ASTManager& operator=(ASTManager&& other) noexcept = default;
+
+ void Init();
+
+ void DeclareLabel(u32 address);
+
+ void InsertLabel(u32 address);
+
+ void InsertGoto(Expr condition, u32 address);
+
+ void InsertBlock(u32 start_address, u32 end_address);
+
+ void InsertReturn(Expr condition, bool kills);
+
+ std::string Print();
+
+ void Decompile();
+
+ void ShowCurrentState(std::string_view state);
+
+ void SanityCheck();
+
+ void Clear();
+
+ bool IsFullyDecompiled() const {
+ if (full_decompile) {
+ return gotos.empty();
+ }
+
+ for (ASTNode goto_node : gotos) {
+ auto label_index = goto_node->GetGotoLabel();
+ if (!label_index) {
+ return false;
+ }
+ ASTNode glabel = labels[*label_index];
+ if (IsBackwardsJump(goto_node, glabel)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ ASTNode GetProgram() const {
+ return main_node;
+ }
+
+ u32 GetVariables() const {
+ return variables;
+ }
+
+ const std::vector<ASTNode>& GetLabels() const {
+ return labels;
+ }
+
+private:
+ bool IsBackwardsJump(ASTNode goto_node, ASTNode label_node) const;
+
+ bool IndirectlyRelated(const ASTNode& first, const ASTNode& second) const;
+
+ bool DirectlyRelated(const ASTNode& first, const ASTNode& second) const;
+
+ void EncloseDoWhile(ASTNode goto_node, ASTNode label);
+
+ void EncloseIfThen(ASTNode goto_node, ASTNode label);
+
+ void MoveOutward(ASTNode goto_node);
+
+ u32 NewVariable() {
+ return variables++;
+ }
+
+ bool full_decompile{};
+ bool disable_else_derivation{};
+ std::unordered_map<u32, u32> labels_map{};
+ u32 labels_count{};
+ std::vector<ASTNode> labels{};
+ std::list<ASTNode> gotos{};
+ u32 variables{};
+ ASTProgram* program{};
+ ASTNode main_node{};
+ Expr false_condition{};
+};
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/compiler_settings.cpp b/src/video_core/shader/compiler_settings.cpp
new file mode 100644
index 000000000..cddcbd4f0
--- /dev/null
+++ b/src/video_core/shader/compiler_settings.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/shader/compiler_settings.h"
+
+namespace VideoCommon::Shader {
+
+std::string CompileDepthAsString(const CompileDepth cd) {
+ switch (cd) {
+ case CompileDepth::BruteForce:
+ return "Brute Force Compile";
+ case CompileDepth::FlowStack:
+ return "Simple Flow Stack Mode";
+ case CompileDepth::NoFlowStack:
+ return "Remove Flow Stack";
+ case CompileDepth::DecompileBackwards:
+ return "Decompile Backward Jumps";
+ case CompileDepth::FullDecompile:
+ return "Full Decompilation";
+ default:
+ return "Unknown Compiler Process";
+ }
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/compiler_settings.h b/src/video_core/shader/compiler_settings.h
new file mode 100644
index 000000000..916018c01
--- /dev/null
+++ b/src/video_core/shader/compiler_settings.h
@@ -0,0 +1,26 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "video_core/engines/shader_bytecode.h"
+
+namespace VideoCommon::Shader {
+
+enum class CompileDepth : u32 {
+ BruteForce = 0,
+ FlowStack = 1,
+ NoFlowStack = 2,
+ DecompileBackwards = 3,
+ FullDecompile = 4,
+};
+
+std::string CompileDepthAsString(CompileDepth cd);
+
+struct CompilerSettings {
+ CompileDepth depth{CompileDepth::NoFlowStack};
+ bool disable_else_derivation{true};
+};
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index ec3a76690..268d1aed0 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -4,13 +4,14 @@
#include <list>
#include <map>
+#include <set>
#include <stack>
#include <unordered_map>
-#include <unordered_set>
#include <vector>
#include "common/assert.h"
#include "common/common_types.h"
+#include "video_core/shader/ast.h"
#include "video_core/shader/control_flow.h"
#include "video_core/shader/shader_ir.h"
@@ -64,12 +65,13 @@ struct CFGRebuildState {
std::list<u32> inspect_queries{};
std::list<Query> queries{};
std::unordered_map<u32, u32> registered{};
- std::unordered_set<u32> labels{};
+ std::set<u32> labels{};
std::map<u32, u32> ssy_labels{};
std::map<u32, u32> pbk_labels{};
std::unordered_map<u32, BlockStack> stacks{};
const ProgramCode& program_code;
const std::size_t program_size;
+ ASTManager* manager;
};
enum class BlockCollision : u32 { None, Found, Inside };
@@ -415,38 +417,133 @@ bool TryQuery(CFGRebuildState& state) {
}
} // Anonymous namespace
-std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
- std::size_t program_size, u32 start_address) {
- CFGRebuildState state{program_code, program_size, start_address};
+void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
+ const auto get_expr = ([&](const Condition& cond) -> Expr {
+ Expr result{};
+ if (cond.cc != ConditionCode::T) {
+ result = MakeExpr<ExprCondCode>(cond.cc);
+ }
+ if (cond.predicate != Pred::UnusedIndex) {
+ u32 pred = static_cast<u32>(cond.predicate);
+ bool negate = false;
+ if (pred > 7) {
+ negate = true;
+ pred -= 8;
+ }
+ Expr extra = MakeExpr<ExprPredicate>(pred);
+ if (negate) {
+ extra = MakeExpr<ExprNot>(extra);
+ }
+ if (result) {
+ return MakeExpr<ExprAnd>(extra, result);
+ }
+ return extra;
+ }
+ if (result) {
+ return result;
+ }
+ return MakeExpr<ExprBoolean>(true);
+ });
+ if (branch.address < 0) {
+ if (branch.kill) {
+ mm.InsertReturn(get_expr(branch.condition), true);
+ return;
+ }
+ mm.InsertReturn(get_expr(branch.condition), false);
+ return;
+ }
+ mm.InsertGoto(get_expr(branch.condition), branch.address);
+}
+
+void DecompileShader(CFGRebuildState& state) {
+ state.manager->Init();
+ for (auto label : state.labels) {
+ state.manager->DeclareLabel(label);
+ }
+ for (auto& block : state.block_info) {
+ if (state.labels.count(block.start) != 0) {
+ state.manager->InsertLabel(block.start);
+ }
+ u32 end = block.branch.ignore ? block.end + 1 : block.end;
+ state.manager->InsertBlock(block.start, end);
+ if (!block.branch.ignore) {
+ InsertBranch(*state.manager, block.branch);
+ }
+ }
+ state.manager->Decompile();
+}
+
+std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
+ u32 start_address,
+ const CompilerSettings& settings) {
+ auto result_out = std::make_unique<ShaderCharacteristics>();
+ if (settings.depth == CompileDepth::BruteForce) {
+ result_out->settings.depth = CompileDepth::BruteForce;
+ return result_out;
+ }
+ CFGRebuildState state{program_code, program_size, start_address};
// Inspect Code and generate blocks
state.labels.clear();
state.labels.emplace(start_address);
state.inspect_queries.push_back(state.start);
while (!state.inspect_queries.empty()) {
if (!TryInspectAddress(state)) {
- return {};
+ result_out->settings.depth = CompileDepth::BruteForce;
+ return result_out;
}
}
- // Decompile Stacks
- state.queries.push_back(Query{state.start, {}, {}});
- bool decompiled = true;
- while (!state.queries.empty()) {
- if (!TryQuery(state)) {
- decompiled = false;
- break;
+ bool use_flow_stack = true;
+
+ bool decompiled = false;
+
+ if (settings.depth != CompileDepth::FlowStack) {
+ // Decompile Stacks
+ state.queries.push_back(Query{state.start, {}, {}});
+ decompiled = true;
+ while (!state.queries.empty()) {
+ if (!TryQuery(state)) {
+ decompiled = false;
+ break;
+ }
}
}
+ use_flow_stack = !decompiled;
+
// Sort and organize results
std::sort(state.block_info.begin(), state.block_info.end(),
- [](const BlockInfo& a, const BlockInfo& b) { return a.start < b.start; });
- ShaderCharacteristics result_out{};
- result_out.decompilable = decompiled;
- result_out.start = start_address;
- result_out.end = start_address;
- for (const auto& block : state.block_info) {
+ [](const BlockInfo& a, const BlockInfo& b) -> bool { return a.start < b.start; });
+ if (decompiled && settings.depth != CompileDepth::NoFlowStack) {
+ ASTManager manager{settings.depth != CompileDepth::DecompileBackwards,
+ settings.disable_else_derivation};
+ state.manager = &manager;
+ DecompileShader(state);
+ decompiled = state.manager->IsFullyDecompiled();
+ if (!decompiled) {
+ if (settings.depth == CompileDepth::FullDecompile) {
+ LOG_CRITICAL(HW_GPU, "Failed to remove all the gotos!:");
+ } else {
+ LOG_CRITICAL(HW_GPU, "Failed to remove all backward gotos!:");
+ }
+ state.manager->ShowCurrentState("Of Shader");
+ state.manager->Clear();
+ } else {
+ auto characteristics = std::make_unique<ShaderCharacteristics>();
+ characteristics->start = start_address;
+ characteristics->settings.depth = settings.depth;
+ characteristics->manager = std::move(manager);
+ characteristics->end = state.block_info.back().end + 1;
+ return characteristics;
+ }
+ }
+
+ result_out->start = start_address;
+ result_out->settings.depth =
+ use_flow_stack ? CompileDepth::FlowStack : CompileDepth::NoFlowStack;
+ result_out->blocks.clear();
+ for (auto& block : state.block_info) {
ShaderBlock new_block{};
new_block.start = block.start;
new_block.end = block.end;
@@ -456,26 +553,26 @@ std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
new_block.branch.kills = block.branch.kill;
new_block.branch.address = block.branch.address;
}
- result_out.end = std::max(result_out.end, block.end);
- result_out.blocks.push_back(new_block);
+ result_out->end = std::max(result_out->end, block.end);
+ result_out->blocks.push_back(new_block);
}
- if (result_out.decompilable) {
- result_out.labels = std::move(state.labels);
- return {std::move(result_out)};
+ if (!use_flow_stack) {
+ result_out->labels = std::move(state.labels);
+ return result_out;
}
- // If it's not decompilable, merge the unlabelled blocks together
- auto back = result_out.blocks.begin();
+ auto back = result_out->blocks.begin();
auto next = std::next(back);
- while (next != result_out.blocks.end()) {
+ while (next != result_out->blocks.end()) {
if (state.labels.count(next->start) == 0 && next->start == back->end + 1) {
back->end = next->end;
- next = result_out.blocks.erase(next);
+ next = result_out->blocks.erase(next);
continue;
}
back = next;
++next;
}
- return {std::move(result_out)};
+
+ return result_out;
}
} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h
index b0a5e4f8c..74e54a5c7 100644
--- a/src/video_core/shader/control_flow.h
+++ b/src/video_core/shader/control_flow.h
@@ -6,9 +6,11 @@
#include <list>
#include <optional>
-#include <unordered_set>
+#include <set>
#include "video_core/engines/shader_bytecode.h"
+#include "video_core/shader/ast.h"
+#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/shader_ir.h"
namespace VideoCommon::Shader {
@@ -67,13 +69,15 @@ struct ShaderBlock {
struct ShaderCharacteristics {
std::list<ShaderBlock> blocks{};
- bool decompilable{};
+ std::set<u32> labels{};
u32 start{};
u32 end{};
- std::unordered_set<u32> labels{};
+ ASTManager manager{true, true};
+ CompilerSettings settings{};
};
-std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
- std::size_t program_size, u32 start_address);
+std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
+ u32 start_address,
+ const CompilerSettings& settings);
} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index 47a9fd961..2626b1616 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -35,58 +35,138 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
} // namespace
+class ASTDecoder {
+public:
+ ASTDecoder(ShaderIR& ir) : ir(ir) {}
+
+ void operator()(ASTProgram& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(ASTIfThen& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(ASTIfElse& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(ASTBlockEncoded& ast) {}
+
+ void operator()(ASTBlockDecoded& ast) {}
+
+ void operator()(ASTVarSet& ast) {}
+
+ void operator()(ASTLabel& ast) {}
+
+ void operator()(ASTGoto& ast) {}
+
+ void operator()(ASTDoWhile& ast) {
+ ASTNode current = ast.nodes.GetFirst();
+ while (current) {
+ Visit(current);
+ current = current->GetNext();
+ }
+ }
+
+ void operator()(ASTReturn& ast) {}
+
+ void operator()(ASTBreak& ast) {}
+
+ void Visit(ASTNode& node) {
+ std::visit(*this, *node->GetInnerData());
+ if (node->IsBlockEncoded()) {
+ auto block = std::get_if<ASTBlockEncoded>(node->GetInnerData());
+ NodeBlock bb = ir.DecodeRange(block->start, block->end);
+ node->TransformBlockEncoded(std::move(bb));
+ }
+ }
+
+private:
+ ShaderIR& ir;
+};
+
void ShaderIR::Decode() {
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
- disable_flow_stack = false;
- const auto info = ScanFlow(program_code, program_size, main_offset);
- if (info) {
- const auto& shader_info = *info;
- coverage_begin = shader_info.start;
- coverage_end = shader_info.end;
- if (shader_info.decompilable) {
- disable_flow_stack = true;
- const auto insert_block = [this](NodeBlock& nodes, u32 label) {
- if (label == static_cast<u32>(exit_branch)) {
- return;
- }
- basic_blocks.insert({label, nodes});
- };
- const auto& blocks = shader_info.blocks;
- NodeBlock current_block;
- u32 current_label = static_cast<u32>(exit_branch);
- for (auto& block : blocks) {
- if (shader_info.labels.count(block.start) != 0) {
- insert_block(current_block, current_label);
- current_block.clear();
- current_label = block.start;
- }
- if (!block.ignore_branch) {
- DecodeRangeInner(current_block, block.start, block.end);
- InsertControlFlow(current_block, block);
- } else {
- DecodeRangeInner(current_block, block.start, block.end + 1);
- }
- }
- insert_block(current_block, current_label);
- return;
- }
- LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method");
- // we can't decompile it, fallback to standard method
+ decompiled = false;
+ auto info = ScanFlow(program_code, program_size, main_offset, settings);
+ auto& shader_info = *info;
+ coverage_begin = shader_info.start;
+ coverage_end = shader_info.end;
+ switch (shader_info.settings.depth) {
+ case CompileDepth::FlowStack: {
for (const auto& block : shader_info.blocks) {
basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)});
}
- return;
+ break;
}
- LOG_WARNING(HW_GPU, "Flow Analysis Failed! Falling back to brute force compiling");
-
- // Now we need to deal with an undecompilable shader. We need to brute force
- // a shader that captures every position.
- coverage_begin = main_offset;
- const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
- coverage_end = shader_end;
- for (u32 label = main_offset; label < shader_end; label++) {
- basic_blocks.insert({label, DecodeRange(label, label + 1)});
+ case CompileDepth::NoFlowStack: {
+ disable_flow_stack = true;
+ const auto insert_block = [this](NodeBlock& nodes, u32 label) {
+ if (label == static_cast<u32>(exit_branch)) {
+ return;
+ }
+ basic_blocks.insert({label, nodes});
+ };
+ const auto& blocks = shader_info.blocks;
+ NodeBlock current_block;
+ u32 current_label = static_cast<u32>(exit_branch);
+ for (auto& block : blocks) {
+ if (shader_info.labels.count(block.start) != 0) {
+ insert_block(current_block, current_label);
+ current_block.clear();
+ current_label = block.start;
+ }
+ if (!block.ignore_branch) {
+ DecodeRangeInner(current_block, block.start, block.end);
+ InsertControlFlow(current_block, block);
+ } else {
+ DecodeRangeInner(current_block, block.start, block.end + 1);
+ }
+ }
+ insert_block(current_block, current_label);
+ break;
+ }
+ case CompileDepth::DecompileBackwards:
+ case CompileDepth::FullDecompile: {
+ program_manager = std::move(shader_info.manager);
+ disable_flow_stack = true;
+ decompiled = true;
+ ASTDecoder decoder{*this};
+ ASTNode program = GetASTProgram();
+ decoder.Visit(program);
+ break;
+ }
+ default:
+ LOG_CRITICAL(HW_GPU, "Unknown decompilation mode!");
+ [[fallthrough]];
+ case CompileDepth::BruteForce: {
+ coverage_begin = main_offset;
+ const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
+ coverage_end = shader_end;
+ for (u32 label = main_offset; label < shader_end; label++) {
+ basic_blocks.insert({label, DecodeRange(label, label + 1)});
+ }
+ break;
+ }
+ }
+ if (settings.depth != shader_info.settings.depth) {
+ LOG_WARNING(
+ HW_GPU, "Decompiling to this setting \"{}\" failed, downgrading to this setting \"{}\"",
+ CompileDepthAsString(settings.depth), CompileDepthAsString(shader_info.settings.depth));
}
}
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index 840694527..fec8f2dbe 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -4,6 +4,7 @@
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/logging/log.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/shader/node_helper.h"
#include "video_core/shader/shader_ir.h"
@@ -18,7 +19,7 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- DEBUG_ASSERT(instr.hsetp2.ftz == 0);
+ LOG_DEBUG(HW_GPU, "ftz={}", static_cast<u32>(instr.hsetp2.ftz));
Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a);
op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a);
@@ -32,6 +33,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
h_and = instr.hsetp2.cbuf_and_imm.h_and;
op_b = GetOperandAbsNegHalf(GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
instr.hsetp2.cbuf.abs_b, instr.hsetp2.cbuf.negate_b);
+ // F32 is hardcoded in hardware
+ op_b = UnpackHalfFloat(std::move(op_b), Tegra::Shader::HalfType::F32);
break;
case OpCode::Id::HSETP2_IMM:
cond = instr.hsetp2.cbuf_and_imm.cond;
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index d54fb88c9..95ec1cdd9 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -41,11 +41,46 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
+ const auto GetCoordinates = [this, instr](Tegra::Shader::ImageType image_type) {
+ std::vector<Node> coords;
+ const std::size_t num_coords{GetImageTypeNumCoordinates(image_type)};
+ coords.reserve(num_coords);
+ for (std::size_t i = 0; i < num_coords; ++i) {
+ coords.push_back(GetRegister(instr.gpr8.Value() + i));
+ }
+ return coords;
+ };
+
switch (opcode->get().GetId()) {
+ case OpCode::Id::SULD: {
+ UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P);
+ UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store !=
+ Tegra::Shader::OutOfBoundsStore::Ignore);
+
+ const auto type{instr.suldst.image_type};
+ auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type)
+ : GetBindlessImage(instr.gpr39, type)};
+ image.MarkRead();
+
+ u32 indexer = 0;
+ for (u32 element = 0; element < 4; ++element) {
+ if (!instr.suldst.IsComponentEnabled(element)) {
+ continue;
+ }
+ MetaImage meta{image, {}, element};
+ Node value = Operation(OperationCode::ImageLoad, meta, GetCoordinates(type));
+ SetTemporary(bb, indexer++, std::move(value));
+ }
+ for (u32 i = 0; i < indexer; ++i) {
+ SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
+ }
+ break;
+ }
case OpCode::Id::SUST: {
- UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P);
- UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore);
- UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store
+ UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P);
+ UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store !=
+ Tegra::Shader::OutOfBoundsStore::Ignore);
+ UNIMPLEMENTED_IF(instr.suldst.component_mask_selector != 0xf); // Ensure we have RGBA
std::vector<Node> values;
constexpr std::size_t hardcoded_size{4};
@@ -53,58 +88,51 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
values.push_back(GetRegister(instr.gpr0.Value() + i));
}
- std::vector<Node> coords;
- const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)};
- for (std::size_t i = 0; i < num_coords; ++i) {
- coords.push_back(GetRegister(instr.gpr8.Value() + i));
- }
-
- const auto type{instr.sust.image_type};
- auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
- : GetBindlessImage(instr.gpr39, type)};
+ const auto type{instr.suldst.image_type};
+ auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type)
+ : GetBindlessImage(instr.gpr39, type)};
image.MarkWrite();
- MetaImage meta{image, values};
- bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords)));
+ MetaImage meta{image, std::move(values)};
+ bb.push_back(Operation(OperationCode::ImageStore, meta, GetCoordinates(type)));
break;
}
case OpCode::Id::SUATOM: {
UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0);
- Node value = GetRegister(instr.gpr0);
-
- std::vector<Node> coords;
- const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)};
- for (std::size_t i = 0; i < num_coords; ++i) {
- coords.push_back(GetRegister(instr.gpr8.Value() + i));
- }
-
const OperationCode operation_code = [instr] {
- switch (instr.suatom_d.operation) {
- case Tegra::Shader::ImageAtomicOperation::Add:
- return OperationCode::AtomicImageAdd;
- case Tegra::Shader::ImageAtomicOperation::Min:
- return OperationCode::AtomicImageMin;
- case Tegra::Shader::ImageAtomicOperation::Max:
- return OperationCode::AtomicImageMax;
- case Tegra::Shader::ImageAtomicOperation::And:
- return OperationCode::AtomicImageAnd;
- case Tegra::Shader::ImageAtomicOperation::Or:
- return OperationCode::AtomicImageOr;
- case Tegra::Shader::ImageAtomicOperation::Xor:
- return OperationCode::AtomicImageXor;
- case Tegra::Shader::ImageAtomicOperation::Exch:
- return OperationCode::AtomicImageExchange;
+ switch (instr.suatom_d.operation_type) {
+ case Tegra::Shader::ImageAtomicOperationType::S32:
+ case Tegra::Shader::ImageAtomicOperationType::U32:
+ switch (instr.suatom_d.operation) {
+ case Tegra::Shader::ImageAtomicOperation::Add:
+ return OperationCode::AtomicImageAdd;
+ case Tegra::Shader::ImageAtomicOperation::And:
+ return OperationCode::AtomicImageAnd;
+ case Tegra::Shader::ImageAtomicOperation::Or:
+ return OperationCode::AtomicImageOr;
+ case Tegra::Shader::ImageAtomicOperation::Xor:
+ return OperationCode::AtomicImageXor;
+ case Tegra::Shader::ImageAtomicOperation::Exch:
+ return OperationCode::AtomicImageExchange;
+ }
default:
- UNIMPLEMENTED_MSG("Unimplemented operation={}",
- static_cast<u32>(instr.suatom_d.operation.Value()));
- return OperationCode::AtomicImageAdd;
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented operation={} type={}",
+ static_cast<u64>(instr.suatom_d.operation.Value()),
+ static_cast<u64>(instr.suatom_d.operation_type.Value()));
+ return OperationCode::AtomicImageAdd;
}();
- const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)};
+ Node value = GetRegister(instr.gpr0);
+
+ const auto type = instr.suatom_d.image_type;
+ auto& image = GetImage(instr.image, type);
+ image.MarkAtomic();
+
MetaImage meta{image, {std::move(value)}};
- SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords)));
+ SetRegister(bb, instr.gpr0, Operation(operation_code, meta, GetCoordinates(type)));
break;
}
default:
@@ -114,35 +142,32 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
return pc;
}
-Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size) {
+Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
const auto offset{static_cast<std::size_t>(image.index.Value())};
- if (const auto image = TryUseExistingImage(offset, type, size)) {
+ if (const auto image = TryUseExistingImage(offset, type)) {
return *image;
}
const std::size_t next_index{used_images.size()};
- return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second;
+ return used_images.emplace(offset, Image{offset, next_index, type}).first->second;
}
-Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size) {
+Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
const Node image_register{GetRegister(reg)};
const auto [base_image, cbuf_index, cbuf_offset]{
TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))};
const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)};
- if (const auto image = TryUseExistingImage(cbuf_key, type, size)) {
+ if (const auto image = TryUseExistingImage(cbuf_key, type)) {
return *image;
}
const std::size_t next_index{used_images.size()};
- return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size})
+ return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type})
.first->second;
}
-Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size) {
+Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type) {
auto it = used_images.find(offset);
if (it == used_images.end()) {
return nullptr;
@@ -150,14 +175,6 @@ Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
auto& image = it->second;
ASSERT(image.GetType() == type);
- if (size) {
- // We know the size, if it's known it has to be the same as before, otherwise we can set it.
- if (image.IsSizeKnown()) {
- ASSERT(image.GetSize() == size);
- } else {
- image.SetSize(*size);
- }
- }
return &image;
}
diff --git a/src/video_core/shader/expr.cpp b/src/video_core/shader/expr.cpp
new file mode 100644
index 000000000..2647865d4
--- /dev/null
+++ b/src/video_core/shader/expr.cpp
@@ -0,0 +1,93 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <variant>
+
+#include "video_core/shader/expr.h"
+
+namespace VideoCommon::Shader {
+namespace {
+bool ExprIsBoolean(const Expr& expr) {
+ return std::holds_alternative<ExprBoolean>(*expr);
+}
+
+bool ExprBooleanGet(const Expr& expr) {
+ return std::get_if<ExprBoolean>(expr.get())->value;
+}
+} // Anonymous namespace
+
+bool ExprAnd::operator==(const ExprAnd& b) const {
+ return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
+}
+
+bool ExprAnd::operator!=(const ExprAnd& b) const {
+ return !operator==(b);
+}
+
+bool ExprOr::operator==(const ExprOr& b) const {
+ return (*operand1 == *b.operand1) && (*operand2 == *b.operand2);
+}
+
+bool ExprOr::operator!=(const ExprOr& b) const {
+ return !operator==(b);
+}
+
+bool ExprNot::operator==(const ExprNot& b) const {
+ return *operand1 == *b.operand1;
+}
+
+bool ExprNot::operator!=(const ExprNot& b) const {
+ return !operator==(b);
+}
+
+Expr MakeExprNot(Expr first) {
+ if (std::holds_alternative<ExprNot>(*first)) {
+ return std::get_if<ExprNot>(first.get())->operand1;
+ }
+ return MakeExpr<ExprNot>(std::move(first));
+}
+
+Expr MakeExprAnd(Expr first, Expr second) {
+ if (ExprIsBoolean(first)) {
+ return ExprBooleanGet(first) ? second : first;
+ }
+ if (ExprIsBoolean(second)) {
+ return ExprBooleanGet(second) ? first : second;
+ }
+ return MakeExpr<ExprAnd>(std::move(first), std::move(second));
+}
+
+Expr MakeExprOr(Expr first, Expr second) {
+ if (ExprIsBoolean(first)) {
+ return ExprBooleanGet(first) ? first : second;
+ }
+ if (ExprIsBoolean(second)) {
+ return ExprBooleanGet(second) ? second : first;
+ }
+ return MakeExpr<ExprOr>(std::move(first), std::move(second));
+}
+
+bool ExprAreEqual(const Expr& first, const Expr& second) {
+ return (*first) == (*second);
+}
+
+bool ExprAreOpposite(const Expr& first, const Expr& second) {
+ if (std::holds_alternative<ExprNot>(*first)) {
+ return ExprAreEqual(std::get_if<ExprNot>(first.get())->operand1, second);
+ }
+ if (std::holds_alternative<ExprNot>(*second)) {
+ return ExprAreEqual(std::get_if<ExprNot>(second.get())->operand1, first);
+ }
+ return false;
+}
+
+bool ExprIsTrue(const Expr& first) {
+ if (ExprIsBoolean(first)) {
+ return ExprBooleanGet(first);
+ }
+ return false;
+}
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h
new file mode 100644
index 000000000..d3dcd00ec
--- /dev/null
+++ b/src/video_core/shader/expr.h
@@ -0,0 +1,139 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <variant>
+
+#include "video_core/engines/shader_bytecode.h"
+
+namespace VideoCommon::Shader {
+
+using Tegra::Shader::ConditionCode;
+using Tegra::Shader::Pred;
+
+class ExprAnd;
+class ExprBoolean;
+class ExprCondCode;
+class ExprNot;
+class ExprOr;
+class ExprPredicate;
+class ExprVar;
+
+using ExprData =
+ std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
+using Expr = std::shared_ptr<ExprData>;
+
+class ExprAnd final {
+public:
+ explicit ExprAnd(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
+
+ bool operator==(const ExprAnd& b) const;
+ bool operator!=(const ExprAnd& b) const;
+
+ Expr operand1;
+ Expr operand2;
+};
+
+class ExprOr final {
+public:
+ explicit ExprOr(Expr a, Expr b) : operand1{std::move(a)}, operand2{std::move(b)} {}
+
+ bool operator==(const ExprOr& b) const;
+ bool operator!=(const ExprOr& b) const;
+
+ Expr operand1;
+ Expr operand2;
+};
+
+class ExprNot final {
+public:
+ explicit ExprNot(Expr a) : operand1{std::move(a)} {}
+
+ bool operator==(const ExprNot& b) const;
+ bool operator!=(const ExprNot& b) const;
+
+ Expr operand1;
+};
+
+class ExprVar final {
+public:
+ explicit ExprVar(u32 index) : var_index{index} {}
+
+ bool operator==(const ExprVar& b) const {
+ return var_index == b.var_index;
+ }
+
+ bool operator!=(const ExprVar& b) const {
+ return !operator==(b);
+ }
+
+ u32 var_index;
+};
+
+class ExprPredicate final {
+public:
+ explicit ExprPredicate(u32 predicate) : predicate{predicate} {}
+
+ bool operator==(const ExprPredicate& b) const {
+ return predicate == b.predicate;
+ }
+
+ bool operator!=(const ExprPredicate& b) const {
+ return !operator==(b);
+ }
+
+ u32 predicate;
+};
+
+class ExprCondCode final {
+public:
+ explicit ExprCondCode(ConditionCode cc) : cc{cc} {}
+
+ bool operator==(const ExprCondCode& b) const {
+ return cc == b.cc;
+ }
+
+ bool operator!=(const ExprCondCode& b) const {
+ return !operator==(b);
+ }
+
+ ConditionCode cc;
+};
+
+class ExprBoolean final {
+public:
+ explicit ExprBoolean(bool val) : value{val} {}
+
+ bool operator==(const ExprBoolean& b) const {
+ return value == b.value;
+ }
+
+ bool operator!=(const ExprBoolean& b) const {
+ return !operator==(b);
+ }
+
+ bool value;
+};
+
+template <typename T, typename... Args>
+Expr MakeExpr(Args&&... args) {
+ static_assert(std::is_convertible_v<T, ExprData>);
+ return std::make_shared<ExprData>(T(std::forward<Args>(args)...));
+}
+
+bool ExprAreEqual(const Expr& first, const Expr& second);
+
+bool ExprAreOpposite(const Expr& first, const Expr& second);
+
+Expr MakeExprNot(Expr first);
+
+Expr MakeExprAnd(Expr first, Expr second);
+
+Expr MakeExprOr(Expr first, Expr second);
+
+bool ExprIsTrue(const Expr& first);
+
+} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index abf2cb1ab..338bab17c 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -149,10 +149,10 @@ enum class OperationCode {
TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
TexelFetch, /// (MetaTexture, int[N], int) -> float4
- ImageStore, /// (MetaImage, int[N] values) -> void
+ ImageLoad, /// (MetaImage, int[N] coords) -> void
+ ImageStore, /// (MetaImage, int[N] coords) -> void
+
AtomicImageAdd, /// (MetaImage, int[N] coords) -> void
- AtomicImageMin, /// (MetaImage, int[N] coords) -> void
- AtomicImageMax, /// (MetaImage, int[N] coords) -> void
AtomicImageAnd, /// (MetaImage, int[N] coords) -> void
AtomicImageOr, /// (MetaImage, int[N] coords) -> void
AtomicImageXor, /// (MetaImage, int[N] coords) -> void
@@ -294,21 +294,18 @@ private:
class Image final {
public:
- constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size)
- : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {}
+ constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type)
+ : offset{offset}, index{index}, type{type}, is_bindless{false} {}
constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
- Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size)
+ Tegra::Shader::ImageType type)
: offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
- is_bindless{true}, size{size} {}
+ is_bindless{true} {}
constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
- bool is_bindless, bool is_written, bool is_read,
- std::optional<Tegra::Shader::ImageAtomicSize> size)
+ bool is_bindless, bool is_written, bool is_read, bool is_atomic)
: offset{offset}, index{index}, type{type}, is_bindless{is_bindless},
- is_written{is_written}, is_read{is_read}, size{size} {}
+ is_written{is_written}, is_read{is_read}, is_atomic{is_atomic} {}
void MarkWrite() {
is_written = true;
@@ -318,8 +315,10 @@ public:
is_read = true;
}
- void SetSize(Tegra::Shader::ImageAtomicSize size_) {
- size = size_;
+ void MarkAtomic() {
+ MarkWrite();
+ MarkRead();
+ is_atomic = true;
}
constexpr std::size_t GetOffset() const {
@@ -346,21 +345,17 @@ public:
return is_read;
}
- constexpr std::pair<u32, u32> GetBindlessCBuf() const {
- return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
+ constexpr bool IsAtomic() const {
+ return is_atomic;
}
- constexpr bool IsSizeKnown() const {
- return size.has_value();
- }
-
- constexpr Tegra::Shader::ImageAtomicSize GetSize() const {
- return size.value();
+ constexpr std::pair<u32, u32> GetBindlessCBuf() const {
+ return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
}
constexpr bool operator<(const Image& rhs) const {
- return std::tie(offset, index, type, size, is_bindless) <
- std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless);
+ return std::tie(offset, index, type, is_bindless) <
+ std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless);
}
private:
@@ -370,7 +365,7 @@ private:
bool is_bindless{};
bool is_written{};
bool is_read{};
- std::optional<Tegra::Shader::ImageAtomicSize> size{};
+ bool is_atomic{};
};
struct GlobalMemoryBase {
@@ -402,6 +397,7 @@ struct MetaTexture {
struct MetaImage {
const Image& image;
std::vector<Node> values;
+ u32 element{};
};
/// Parameters that modify an operation but are not part of any particular operand
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 2c357f310..c1f2b88c8 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -22,8 +22,10 @@ using Tegra::Shader::PredCondition;
using Tegra::Shader::PredOperation;
using Tegra::Shader::Register;
-ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size)
- : program_code{program_code}, main_offset{main_offset}, program_size{size} {
+ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size,
+ CompilerSettings settings)
+ : program_code{program_code}, main_offset{main_offset}, program_size{size}, basic_blocks{},
+ program_manager{true, true}, settings{settings} {
Decode();
}
@@ -137,7 +139,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff
return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer));
}
-Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) {
+Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) const {
const Node node = MakeNode<InternalFlagNode>(flag);
if (negated) {
return Operation(OperationCode::LogicalNegate, node);
@@ -367,13 +369,13 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
return op->second;
}
-Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) {
+Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const {
switch (cc) {
case Tegra::Shader::ConditionCode::NEU:
return GetInternalFlag(InternalFlag::Zero, true);
default:
UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc));
- return GetPredicate(static_cast<u64>(Pred::NeverExecute));
+ return MakeNode<PredicateNode>(Pred::NeverExecute, false);
}
}
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 2f03d83ba..105981d67 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -15,6 +15,8 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h"
+#include "video_core/shader/ast.h"
+#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/node.h"
namespace VideoCommon::Shader {
@@ -64,7 +66,8 @@ struct GlobalMemoryUsage {
class ShaderIR final {
public:
- explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size);
+ explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size,
+ CompilerSettings settings);
~ShaderIR();
const std::map<u32, NodeBlock>& GetBasicBlocks() const {
@@ -144,11 +147,31 @@ public:
return disable_flow_stack;
}
+ bool IsDecompiled() const {
+ return decompiled;
+ }
+
+ const ASTManager& GetASTManager() const {
+ return program_manager;
+ }
+
+ ASTNode GetASTProgram() const {
+ return program_manager.GetProgram();
+ }
+
+ u32 GetASTNumVariables() const {
+ return program_manager.GetVariables();
+ }
+
u32 ConvertAddressToNvidiaSpace(const u32 address) const {
return (address - main_offset) * sizeof(Tegra::Shader::Instruction);
}
+ /// Returns a condition code evaluated from internal flags
+ Node GetConditionCode(Tegra::Shader::ConditionCode cc) const;
+
private:
+ friend class ASTDecoder;
void Decode();
NodeBlock DecodeRange(u32 begin, u32 end);
@@ -213,7 +236,7 @@ private:
/// Generates a node representing an output attribute. Keeps track of used attributes.
Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
/// Generates a node representing an internal flag
- Node GetInternalFlag(InternalFlag flag, bool negated = false);
+ Node GetInternalFlag(InternalFlag flag, bool negated = false) const;
/// Generates a node representing a local memory address
Node GetLocalMemory(Node address);
/// Generates a node representing a shared memory address
@@ -271,9 +294,6 @@ private:
/// Returns a predicate combiner operation
OperationCode GetPredicateCombiner(Tegra::Shader::PredOperation operation);
- /// Returns a condition code evaluated from internal flags
- Node GetConditionCode(Tegra::Shader::ConditionCode cc);
-
/// Accesses a texture sampler
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
@@ -284,16 +304,13 @@ private:
bool is_shadow);
/// Accesses an image.
- Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size = {});
+ Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
/// Access a bindless image sampler.
- Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size = {});
+ Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
/// Tries to access an existing image, updating it's state as needed
- Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
- std::optional<Tegra::Shader::ImageAtomicSize> size);
+ Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type);
/// Extracts a sequence of bits from a node
Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -360,6 +377,7 @@ private:
const ProgramCode& program_code;
const u32 main_offset;
const std::size_t program_size;
+ bool decompiled{};
bool disable_flow_stack{};
u32 coverage_begin{};
@@ -367,6 +385,8 @@ private:
std::map<u32, NodeBlock> basic_blocks;
NodeBlock global_code;
+ ASTManager program_manager;
+ CompilerSettings settings{};
std::set<u32> used_registers;
std::set<Tegra::Shader::Pred> used_predicates;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 877c6635d..ca2da8f97 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -224,8 +224,13 @@ public:
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config,
const Tegra::Engines::Fermi2D::Config& copy_config) {
std::lock_guard lock{mutex};
- std::pair<TSurface, TView> dst_surface = GetFermiSurface(dst_config);
- std::pair<TSurface, TView> src_surface = GetFermiSurface(src_config);
+ SurfaceParams src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
+ SurfaceParams dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
+ const GPUVAddr src_gpu_addr = src_config.Address();
+ const GPUVAddr dst_gpu_addr = dst_config.Address();
+ DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
+ std::pair<TSurface, TView> dst_surface = GetSurface(dst_gpu_addr, dst_params, true, false);
+ std::pair<TSurface, TView> src_surface = GetSurface(src_gpu_addr, src_params, true, false);
ImageBlit(src_surface.second, dst_surface.second, copy_config);
dst_surface.first->MarkAsModified(true, Tick());
}
@@ -357,6 +362,29 @@ private:
BufferCopy = 3,
};
+ enum class DeductionType : u32 {
+ DeductionComplete,
+ DeductionIncomplete,
+ DeductionFailed,
+ };
+
+ struct Deduction {
+ DeductionType type{DeductionType::DeductionFailed};
+ TSurface surface{};
+
+ bool Failed() const {
+ return type == DeductionType::DeductionFailed;
+ }
+
+ bool Incomplete() const {
+ return type == DeductionType::DeductionIncomplete;
+ }
+
+ bool IsDepth() const {
+ return surface->GetSurfaceParams().IsPixelFormatZeta();
+ }
+ };
+
/**
* `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle.
* @param overlaps, the overlapping surfaces registered in the cache.
@@ -691,6 +719,120 @@ private:
MatchTopologyResult::FullMatch);
}
+ /**
+ * `DeduceSurface` gets the starting address and parameters of a candidate surface and tries
+ * to find a matching surface within the cache that's similar to it. If there are many textures
+ * or the texture found if entirely incompatible, it will fail. If no texture is found, the
+ * blit will be unsuccessful.
+ * @param gpu_addr, the starting address of the candidate surface.
+ * @param params, the paremeters on the candidate surface.
+ **/
+ Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
+ const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
+ const auto cache_addr{ToCacheAddr(host_ptr)};
+
+ if (!cache_addr) {
+ Deduction result{};
+ result.type = DeductionType::DeductionFailed;
+ return result;
+ }
+
+ if (const auto iter = l1_cache.find(cache_addr); iter != l1_cache.end()) {
+ TSurface& current_surface = iter->second;
+ const auto topological_result = current_surface->MatchesTopology(params);
+ if (topological_result != MatchTopologyResult::FullMatch) {
+ Deduction result{};
+ result.type = DeductionType::DeductionFailed;
+ return result;
+ }
+ const auto struct_result = current_surface->MatchesStructure(params);
+ if (struct_result != MatchStructureResult::None &&
+ current_surface->MatchTarget(params.target)) {
+ Deduction result{};
+ result.type = DeductionType::DeductionComplete;
+ result.surface = current_surface;
+ return result;
+ }
+ }
+
+ const std::size_t candidate_size = params.GetGuestSizeInBytes();
+ auto overlaps{GetSurfacesInRegion(cache_addr, candidate_size)};
+
+ if (overlaps.empty()) {
+ Deduction result{};
+ result.type = DeductionType::DeductionIncomplete;
+ return result;
+ }
+
+ if (overlaps.size() > 1) {
+ Deduction result{};
+ result.type = DeductionType::DeductionFailed;
+ return result;
+ } else {
+ Deduction result{};
+ result.type = DeductionType::DeductionComplete;
+ result.surface = overlaps[0];
+ return result;
+ }
+ }
+
+ /**
+ * `DeduceBestBlit` gets the a source and destination starting address and parameters,
+ * and tries to deduce if they are supposed to be depth textures. If so, their
+ * parameters are modified and fixed into so.
+ * @param gpu_addr, the starting address of the candidate surface.
+ * @param params, the parameters on the candidate surface.
+ **/
+ void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params,
+ const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) {
+ auto deduced_src = DeduceSurface(src_gpu_addr, src_params);
+ auto deduced_dst = DeduceSurface(src_gpu_addr, src_params);
+ if (deduced_src.Failed() || deduced_dst.Failed()) {
+ return;
+ }
+
+ const bool incomplete_src = deduced_src.Incomplete();
+ const bool incomplete_dst = deduced_dst.Incomplete();
+
+ if (incomplete_src && incomplete_dst) {
+ return;
+ }
+
+ const bool any_incomplete = incomplete_src || incomplete_dst;
+
+ if (!any_incomplete) {
+ if (!(deduced_src.IsDepth() && deduced_dst.IsDepth())) {
+ return;
+ }
+ } else {
+ if (incomplete_src && !(deduced_dst.IsDepth())) {
+ return;
+ }
+
+ if (incomplete_dst && !(deduced_src.IsDepth())) {
+ return;
+ }
+ }
+
+ const auto inherit_format = ([](SurfaceParams& to, TSurface from) {
+ const SurfaceParams& params = from->GetSurfaceParams();
+ to.pixel_format = params.pixel_format;
+ to.component_type = params.component_type;
+ to.type = params.type;
+ });
+ // Now we got the cases where one or both is Depth and the other is not known
+ if (!incomplete_src) {
+ inherit_format(src_params, deduced_src.surface);
+ } else {
+ inherit_format(src_params, deduced_dst.surface);
+ }
+ if (!incomplete_dst) {
+ inherit_format(dst_params, deduced_dst.surface);
+ } else {
+ inherit_format(dst_params, deduced_src.surface);
+ }
+ }
+
std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params,
bool preserve_contents) {
auto new_surface{GetUncachedSurface(gpu_addr, params)};
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index dc6fa07fc..ff1c1d985 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -66,6 +66,9 @@ add_executable(yuzu
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_profile_manager.ui
+ configuration/configure_service.cpp
+ configuration/configure_service.h
+ configuration/configure_service.ui
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
@@ -186,6 +189,10 @@ if (YUZU_USE_QT_WEB_ENGINE)
target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
endif ()
+if (YUZU_ENABLE_BOXCAT)
+ target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT)
+endif ()
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
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() {
qt_config->endGroup();
}
+void Config::ReadServiceValues() {
+ qt_config->beginGroup(QStringLiteral("Services"));
+ Settings::values.bcat_backend =
+ ReadSetting(QStringLiteral("bcat_backend"), QStringLiteral("boxcat"))
+ .toString()
+ .toStdString();
+ Settings::values.bcat_boxcat_local =
+ ReadSetting(QStringLiteral("bcat_boxcat_local"), false).toBool();
+ qt_config->endGroup();
+}
+
void Config::ReadDisabledAddOnValues() {
const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
@@ -769,6 +780,7 @@ void Config::ReadValues() {
ReadMiscellaneousValues();
ReadDebuggingValues();
ReadWebServiceValues();
+ ReadServiceValues();
ReadDisabledAddOnValues();
ReadUIValues();
}
@@ -866,6 +878,7 @@ void Config::SaveValues() {
SaveMiscellaneousValues();
SaveDebuggingValues();
SaveWebServiceValues();
+ SaveServiceValues();
SaveDisabledAddOnValues();
SaveUIValues();
}
@@ -963,6 +976,14 @@ void Config::SaveDebuggingValues() {
qt_config->endGroup();
}
+void Config::SaveServiceValues() {
+ qt_config->beginGroup(QStringLiteral("Services"));
+ WriteSetting(QStringLiteral("bcat_backend"),
+ QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null"));
+ WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false);
+ qt_config->endGroup();
+}
+
void Config::SaveDisabledAddOnValues() {
qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
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:
void ReadCoreValues();
void ReadDataStorageValues();
void ReadDebuggingValues();
+ void ReadServiceValues();
void ReadDisabledAddOnValues();
void ReadMiscellaneousValues();
void ReadPathValues();
@@ -65,6 +66,7 @@ private:
void SaveCoreValues();
void SaveDataStorageValues();
void SaveDebuggingValues();
+ void SaveServiceValues();
void SaveDisabledAddOnValues();
void SaveMiscellaneousValues();
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 @@
<string>Web</string>
</attribute>
</widget>
+ <widget class="ConfigureService" name="serviceTab">
+ <attribute name="title">
+ <string>Services</string>
+ </attribute>
+ </widget>
</widget>
</item>
</layout>
@@ -178,6 +183,12 @@
<header>configuration/configure_hotkeys.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureService</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_service.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<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() {
ui->audioTab->ApplyConfiguration();
ui->debugTab->ApplyConfiguration();
ui->webTab->ApplyConfiguration();
+ ui->serviceTab->ApplyConfiguration();
Settings::Apply();
Settings::LogSettings();
}
@@ -74,7 +75,8 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
- {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}},
+ {tr("System"),
+ {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}},
{tr("Graphics"), {ui->graphicsTab}},
{tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
};
@@ -108,6 +110,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{ui->webTab, tr("Web")},
{ui->gameListTab, tr("Game List")},
{ui->filesystemTab, tr("Filesystem")},
+ {ui->serviceTab, tr("Services")},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
new file mode 100644
index 000000000..06566e981
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -0,0 +1,138 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QGraphicsItem>
+#include <QtConcurrent/QtConcurrent>
+#include "core/hle/service/bcat/backend/boxcat.h"
+#include "core/settings.h"
+#include "ui_configure_service.h"
+#include "yuzu/configuration/configure_service.h"
+
+namespace {
+QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
+ QString out;
+
+ if (status.header.has_value()) {
+ out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header));
+ }
+
+ if (status.events.size() == 1) {
+ out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front()));
+ } else {
+ for (const auto& event : status.events) {
+ out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event));
+ }
+ }
+
+ if (status.footer.has_value()) {
+ out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer));
+ }
+
+ return out;
+}
+} // Anonymous namespace
+
+ConfigureService::ConfigureService(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
+ ui->setupUi(this);
+
+ ui->bcat_source->addItem(QStringLiteral("None"));
+ ui->bcat_empty_label->setHidden(true);
+ ui->bcat_empty_header->setHidden(true);
+
+#ifdef YUZU_ENABLE_BOXCAT
+ ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
+#endif
+
+ connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+ &ConfigureService::OnBCATImplChanged);
+
+ this->SetConfiguration();
+}
+
+ConfigureService::~ConfigureService() = default;
+
+void ConfigureService::ApplyConfiguration() {
+ Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
+}
+
+void ConfigureService::RetranslateUi() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureService::SetConfiguration() {
+ const int index =
+ ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
+ ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
+}
+
+std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
+ std::optional<std::string> global;
+ std::map<std::string, Service::BCAT::EventStatus> map;
+ const auto res = Service::BCAT::Boxcat::GetStatus(global, map);
+
+ switch (res) {
+ case Service::BCAT::Boxcat::StatusResult::Success:
+ break;
+ case Service::BCAT::Boxcat::StatusResult::Offline:
+ return {QString{},
+ tr("The boxcat service is offline or you are not connected to the internet.")};
+ case Service::BCAT::Boxcat::StatusResult::ParseError:
+ return {QString{},
+ tr("There was an error while processing the boxcat event data. Contact the yuzu "
+ "developers.")};
+ case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
+ return {QString{},
+ tr("The version of yuzu you are using is either too new or too old for the server. "
+ "Try updating to the latest official release of yuzu.")};
+ }
+
+ if (map.empty()) {
+ return {QStringLiteral("Current Boxcat Events"),
+ tr("There are currently no events on boxcat.")};
+ }
+
+ QString out;
+
+ if (global.has_value()) {
+ out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global));
+ }
+
+ for (const auto& [key, value] : map) {
+ out += QStringLiteral("%1<b>%2</b><br>%3")
+ .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
+ .arg(QString::fromStdString(key))
+ .arg(FormatEventStatusString(value));
+ }
+ return {QStringLiteral("Current Boxcat Events"), std::move(out)};
+}
+
+void ConfigureService::OnBCATImplChanged() {
+#ifdef YUZU_ENABLE_BOXCAT
+ const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
+ ui->bcat_empty_header->setHidden(!boxcat);
+ ui->bcat_empty_label->setHidden(!boxcat);
+ ui->bcat_empty_header->setText(QString{});
+ ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
+
+ if (!boxcat)
+ return;
+
+ const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); });
+
+ watcher.setFuture(future);
+ connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this,
+ [this] { OnUpdateBCATEmptyLabel(watcher.result()); });
+#endif
+}
+
+void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
+#ifdef YUZU_ENABLE_BOXCAT
+ const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
+ if (boxcat) {
+ ui->bcat_empty_header->setText(string.first);
+ ui->bcat_empty_label->setText(string.second);
+ }
+#endif
+}
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 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QFutureWatcher>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureService;
+}
+
+class ConfigureService : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureService(QWidget* parent = nullptr);
+ ~ConfigureService() override;
+
+ void ApplyConfiguration();
+ void RetranslateUi();
+
+private:
+ void SetConfiguration();
+
+ std::pair<QString, QString> BCATDownloadEvents();
+ void OnBCATImplChanged();
+ void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
+
+ std::unique_ptr<Ui::ConfigureService> ui;
+ QFutureWatcher<std::pair<QString, QString>> watcher{this};
+};
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureService</class>
+ <widget class="QWidget" name="ConfigureService">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>433</width>
+ <height>561</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>BCAT</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="maximumSize">
+ <size>
+ <width>260</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>BCAT Backend</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QLabel" name="bcat_empty_label">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>260</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <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>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="bcat_source"/>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="bcat_empty_header">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d5fab2f1f..a2b88c787 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -172,9 +172,7 @@ void GameList::onTextChanged(const QString& new_text) {
const int folder_count = tree_view->model()->rowCount();
QString edit_filter_text = new_text.toLower();
QStandardItem* folder;
- QStandardItem* child;
int children_total = 0;
- QModelIndex root_index = item_model->invisibleRootItem()->index();
// If the searchfield is empty every item is visible
// Otherwise the filter gets applied
@@ -272,6 +270,8 @@ void GameList::onUpdateThemedIcons() {
.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
Qt::DecorationRole);
break;
+ default:
+ break;
}
}
}
@@ -392,6 +392,8 @@ void GameList::ValidateEntry(const QModelIndex& item) {
case GameListItemType::AddDir:
emit AddDirectory();
break;
+ default:
+ break;
}
}
@@ -462,6 +464,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
case GameListItemType::SysNandDir:
AddPermDirPopup(context_menu, selected);
break;
+ default:
+ break;
}
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index a8d888fee..1c2b37afd 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -247,7 +247,7 @@ public:
Qt::DecorationRole);
setData(QObject::tr("System Titles"), Qt::DisplayRole);
break;
- case GameListItemType::CustomDir:
+ case GameListItemType::CustomDir: {
const QString icon_name = QFileInfo::exists(game_dir->path)
? QStringLiteral("folder")
: QStringLiteral("bad_folder");
@@ -256,8 +256,11 @@ public:
Qt::DecorationRole);
setData(game_dir->path, Qt::DisplayRole);
break;
- };
- };
+ }
+ default:
+ break;
+ }
+ }
int type() const override {
return static_cast<int>(dir_type);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index fd21a9761..4c81ef12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -326,10 +326,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
} else {
std::vector<u8> icon;
- const auto res1 = loader->ReadIcon(icon);
+ [[maybe_unused]] const auto res1 = loader->ReadIcon(icon);
std::string name = " ";
- const auto res3 = loader->ReadTitle(name);
+ [[maybe_unused]] const auto res3 = loader->ReadTitle(name);
const FileSys::PatchManager patch{program_id};
@@ -354,20 +354,20 @@ void GameListWorker::run() {
for (UISettings::GameDir& game_dir : game_dirs) {
if (game_dir.path == QStringLiteral("SDMC")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
- emit DirEntryReady({game_list_dir});
+ emit DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else if (game_dir.path == QStringLiteral("UserNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
- emit DirEntryReady({game_list_dir});
+ emit DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else if (game_dir.path == QStringLiteral("SysNAND")) {
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
- emit DirEntryReady({game_list_dir});
+ emit DirEntryReady(game_list_dir);
AddTitlesToGameList(game_list_dir);
} else {
watch_list.append(game_dir.path);
auto* const game_list_dir = new GameListDir(game_dir);
- emit DirEntryReady({game_list_dir});
+ emit DirEntryReady(game_list_dir);
provider->ClearAllEntries();
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
game_list_dir);
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 6e52fca89..84e4e1b42 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -75,8 +75,9 @@ private:
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
- QStringList watch_list;
- const CompatibilityList& compatibility_list;
QVector<UISettings::GameDir>& game_dirs;
+ const CompatibilityList& compatibility_list;
+
+ QStringList watch_list;
std::atomic_bool stop_processing;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2d82df739..451e4a4d6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1889,15 +1889,24 @@ void GMainWindow::OnCaptureScreenshot() {
}
void GMainWindow::UpdateWindowTitle(const QString& title_name) {
- const QString full_name = QString::fromUtf8(Common::g_build_fullname);
- const QString branch_name = QString::fromUtf8(Common::g_scm_branch);
- const QString description = QString::fromUtf8(Common::g_scm_desc);
+ const auto full_name = std::string(Common::g_build_fullname);
+ const auto branch_name = std::string(Common::g_scm_branch);
+ const auto description = std::string(Common::g_scm_desc);
+ const auto build_id = std::string(Common::g_build_id);
+
+ const auto date =
+ QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd")).toStdString();
if (title_name.isEmpty()) {
- setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description));
+ const auto fmt = std::string(Common::g_title_bar_format_idle);
+ setWindowTitle(QString::fromStdString(fmt::format(fmt.empty() ? "yuzu {0}| {1}-{2}" : fmt,
+ full_name, branch_name, description,
+ std::string{}, date, build_id)));
} else {
- setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3")
- .arg(full_name, branch_name, description, title_name));
+ const auto fmt = std::string(Common::g_title_bar_format_running);
+ setWindowTitle(QString::fromStdString(
+ fmt::format(fmt.empty() ? "yuzu {0}| {3} | {1}-{2}" : fmt, full_name, branch_name,
+ description, title_name.toStdString(), date, build_id)));
}
}
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 7f7d247a3..43bad9678 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -9,6 +9,8 @@ namespace UISettings {
const Themes themes{{
{"Default", "default"},
{"Dark", "qdarkstyle"},
+ {"Colorful", "colorful"},
+ {"Colorful Dark", "colorful_dark"},
}};
Values values = {};
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index c57290006..a8eaf5f8c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -24,7 +24,7 @@ struct Shortcut {
ContextualShortcut shortcut;
};
-using Themes = std::array<std::pair<const char*, const char*>, 2>;
+using Themes = std::array<std::pair<const char*, const char*>, 4>;
extern const Themes themes;
struct GameDir {
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index d82438502..1a812cb87 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -433,6 +433,11 @@ void Config::ReadValues() {
sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
+
+ // Services
+ Settings::values.bcat_backend = sdl2_config->Get("Services", "bcat_backend", "boxcat");
+ Settings::values.bcat_boxcat_local =
+ sdl2_config->GetBoolean("Services", "bcat_boxcat_local", false);
}
void 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
yuzu_username =
yuzu_token =
+[Services]
+# The name of the backend to use for BCAT
+# If this is set to 'boxcat' boxcat will be used, otherwise a null implementation will be used
+bcat_backend =
+
[AddOns]
# Used to disable add-ons
# List of title IDs of games that will have add-ons disabled (separated by '|'):
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index a6edc089a..b1c512db1 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -4,6 +4,9 @@
#include <SDL.h>
#include "common/logging/log.h"
+#include "common/scm_rev.h"
+#include "core/core.h"
+#include "core/perf_stats.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
@@ -170,6 +173,16 @@ void EmuWindow_SDL2::PollEvents() {
break;
}
}
+
+ const u32 current_time = SDL_GetTicks();
+ if (current_time > last_time + 2000) {
+ const auto results = Core::System::GetInstance().GetAndResetPerfStats();
+ const auto title = fmt::format(
+ "yuzu {} | {}-{} | FPS: {:.0f} ({:.0%})", Common::g_build_fullname,
+ Common::g_scm_branch, Common::g_scm_desc, results.game_fps, results.emulation_speed);
+ SDL_SetWindowTitle(render_window, title.c_str());
+ last_time = current_time;
+ }
}
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index d8051ebdf..eaa971f77 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -60,4 +60,7 @@ protected:
/// Internal SDL2 render window
SDL_Window* render_window;
+
+ /// Keeps track of how often to update the title bar during gameplay
+ u32 last_time = 0;
};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index bac05b959..3ee088a91 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -186,8 +186,6 @@ int main(int argc, char** argv) {
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
- SCOPE_EXIT({ system.Shutdown(); });
-
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
switch (load_result) {
@@ -227,6 +225,8 @@ int main(int argc, char** argv) {
system.RunLoop();
}
+ system.Shutdown();
+
detached_tasks.WaitForAllTasks();
return 0;
}
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index 9a11dc6c3..84ab4d687 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -93,7 +93,6 @@ void Config::ReadValues() {
// System
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
- const auto size = sdl2_config->GetInteger("System", "users_size", 0);
Settings::values.current_user = std::clamp<int>(
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);