From 84b605901299c9a8ea01d461109580710cabfb48 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:41:39 -0400
Subject: [PATCH 01/31] externals: Add zlib and libzip libraries to handle ZIP
 file parsing

---
 .gitmodules              | 6 ++++++
 externals/CMakeLists.txt | 6 ++++++
 externals/libzip         | 1 +
 externals/zlib           | 1 +
 4 files changed, 14 insertions(+)
 create mode 160000 externals/libzip
 create mode 160000 externals/zlib

diff --git a/.gitmodules b/.gitmodules
index 3a49c4874..f3051cca0 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -46,3 +46,9 @@
 [submodule "sirit"]
     path = externals/sirit
     url = https://github.com/ReinUsesLisp/sirit
+[submodule "libzip"]
+	path = externals/libzip
+	url = https://github.com/DarkLordZach/libzip
+[submodule "zlib"]
+	path = externals/zlib
+	url = https://github.com/DarkLordZach/zlib
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index e6fa11a03..d797d9fc9 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -77,6 +77,12 @@ if (ENABLE_VULKAN)
     add_subdirectory(sirit)
 endif()
 
+# libzip
+add_subdirectory(libzip)
+
+# zlib
+add_subdirectory(zlib)
+
 if (ENABLE_WEB_SERVICE)
     # LibreSSL
     set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
diff --git a/externals/libzip b/externals/libzip
new file mode 160000
index 000000000..bebbb54c8
--- /dev/null
+++ b/externals/libzip
@@ -0,0 +1 @@
+Subproject commit bebbb54c8e691f019415fcb852ef4d53ebbc5000
diff --git a/externals/zlib b/externals/zlib
new file mode 160000
index 000000000..094ed57db
--- /dev/null
+++ b/externals/zlib
@@ -0,0 +1 @@
+Subproject commit 094ed57db392170130bc710293568de7b576306d

From c00ed8f4ffc4e6fca6337aecaa1acd390c71a584 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:42:05 -0400
Subject: [PATCH 02/31] vfs: Add function to extract ZIP file into virtual
 filesystem

---
 src/core/file_sys/vfs_libzip.cpp | 83 ++++++++++++++++++++++++++++++++
 src/core/file_sys/vfs_libzip.h   | 13 +++++
 2 files changed, 96 insertions(+)
 create mode 100644 src/core/file_sys/vfs_libzip.cpp
 create mode 100644 src/core/file_sys/vfs_libzip.h

diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
new file mode 100644
index 000000000..64f19a0ea
--- /dev/null
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -0,0 +1,83 @@
+// 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();
+    const auto src = zip_source_buffer_create(data.data(), data.size(), 0, &error);
+    if (src == nullptr)
+        return nullptr;
+
+    const auto zip = zip_open_from_source(src, 0, &error);
+    if (zip == nullptr)
+        return nullptr;
+
+    std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>();
+
+    const auto num_entries = zip_get_num_entries(zip, 0);
+    if (num_entries == -1)
+        return nullptr;
+
+    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, i, 0, &stat);
+        if (stat_res == -1)
+            return nullptr;
+
+        const std::string name(stat.name);
+        if (name.empty())
+            continue;
+
+        if (name[name.size() - 1] != '/') {
+            const auto file = zip_fopen_index(zip, i, 0);
+
+            std::vector<u8> buf(stat.size);
+            if (zip_fread(file, buf.data(), buf.size()) != buf.size())
+                return nullptr;
+
+            zip_fclose(file);
+
+            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);
+        }
+    }
+
+    zip_source_close(src);
+    zip_close(zip);
+
+    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

From f2073217a4b074f53e1932eaf41cf08d6296b21f Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:42:36 -0400
Subject: [PATCH 03/31] filesystem: Add getter for BCAT temporary directory

---
 src/core/file_sys/bis_factory.cpp              | 5 +++++
 src/core/file_sys/bis_factory.h                | 2 ++
 src/core/hle/service/filesystem/filesystem.cpp | 9 +++++++++
 3 files changed, 16 insertions(+)

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/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 14cd0e322..9cb107668 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
     return bis_factory->GetModificationDumpRoot(title_id);
 }
 
+FileSys::VirtualDir GetBCATDirectory(u64 title_id) {
+    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;

From 943662dc3c59537d7c3e05b98cfc08212491ac87 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:43:10 -0400
Subject: [PATCH 04/31] applets: Add accessor for AppletFrontendSet Allows
 other services to call applets without using LLE.

---
 src/core/hle/service/am/applets/applets.cpp | 4 ++++
 src/core/hle/service/am/applets/applets.h   | 2 ++
 2 files changed, 6 insertions(+)

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();

From f6c53526b3e6852577ad5f5986be89202b15c5b2 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:43:48 -0400
Subject: [PATCH 05/31] core/loader: Track the NSO build ID of the current
 process

---
 src/core/core.cpp       | 9 +++++++++
 src/core/core.h         | 4 ++++
 src/core/loader/nso.cpp | 1 +
 3 files changed, 14 insertions(+)

diff --git a/src/core/core.cpp b/src/core/core.cpp
index 92ba42fb9..75a7ffb97 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -339,6 +339,7 @@ struct System::Impl {
 
     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;
@@ -640,6 +641,14 @@ bool System::GetExitLock() const {
     return impl->exit_lock;
 }
 
+void System::SetCurrentProcessBuildID(std::array<u8, 32> id) {
+    impl->build_id = id;
+}
+
+const std::array<u8, 32>& 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 ff10ebe12..f49b7fbf9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -330,6 +330,10 @@ public:
 
     bool GetExitLock() const;
 
+    void SetCurrentProcessBuildID(std::array<u8, 0x20> id);
+
+    const std::array<u8, 0x20>& GetCurrentProcessBuildID() const;
+
 private:
     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);

From 532ec459b8661cd5fe0bdff73758b474a54ed94c Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:44:13 -0400
Subject: [PATCH 06/31] nifm: Signal to applications that internet access is
 available

---
 src/core/hle/service/nifm/nifm.cpp | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

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;
 };

From 647992e666617d287a06b4ffbd1db9ab6cbd524d Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:44:46 -0400
Subject: [PATCH 07/31] settings: Add option to set BCAT backend

---
 src/core/settings.cpp             |  2 ++
 src/core/settings.h               |  4 ++++
 src/yuzu/configuration/config.cpp | 16 ++++++++++++++++
 src/yuzu/configuration/config.h   |  2 ++
 src/yuzu_cmd/config.cpp           |  5 +++++
 src/yuzu_cmd/default_ini.h        |  5 +++++
 6 files changed, 34 insertions(+)

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/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 92d9fb161..ac7a77365 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -525,6 +525,13 @@ void Config::ReadDebuggingValues() {
     qt_config->endGroup();
 }
 
+void Config::ReadServiceValues() {
+    qt_config->beginGroup("Services");
+    Settings::values.bcat_backend = ReadSetting("bcat_backend", "boxcat").toString().toStdString();
+    Settings::values.bcat_boxcat_local = ReadSetting("bcat_boxcat_local", false).toBool();
+    qt_config->endGroup();
+}
+
 void Config::ReadDisabledAddOnValues() {
     const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
 
@@ -769,6 +776,7 @@ void Config::ReadValues() {
     ReadMiscellaneousValues();
     ReadDebuggingValues();
     ReadWebServiceValues();
+    ReadServiceValues();
     ReadDisabledAddOnValues();
     ReadUIValues();
 }
@@ -866,6 +874,7 @@ void Config::SaveValues() {
     SaveMiscellaneousValues();
     SaveDebuggingValues();
     SaveWebServiceValues();
+    SaveServiceValues();
     SaveDisabledAddOnValues();
     SaveUIValues();
 }
@@ -963,6 +972,13 @@ void Config::SaveDebuggingValues() {
     qt_config->endGroup();
 }
 
+void Config::SaveServiceValues() {
+    qt_config->beginGroup("Services");
+    WriteSetting("bcat_backend", QString::fromStdString(Settings::values.bcat_backend), "null");
+    WriteSetting("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_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 '|'):

From 2c0b75a7448ab3878d159548858b397e1bcc305b Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:46:46 -0400
Subject: [PATCH 08/31] bcat: Add backend class to generify the functions of
 BCAT Provides the most abstract simplified functions of BCAT as functions.
 Also includes a NullBackend class which is just a no-op.

---
 src/core/hle/service/bcat/backend/backend.cpp | 47 ++++++++++++++++
 src/core/hle/service/bcat/backend/backend.h   | 53 +++++++++++++++++++
 2 files changed, 100 insertions(+)
 create mode 100644 src/core/hle/service/bcat/backend/backend.cpp
 create mode 100644 src/core/hle/service/bcat/backend/backend.h

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..aefa2208d
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -0,0 +1,47 @@
+// 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/hle/service/bcat/backend/backend.h"
+
+namespace Service::BCAT {
+
+Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
+
+Backend::~Backend() = default;
+
+NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {}
+
+NullBackend::~NullBackend() = default;
+
+bool NullBackend::Synchronize(TitleIDVersion title, CompletionCallback callback) {
+    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
+              title.build_id);
+
+    callback(true);
+    return true;
+}
+
+bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
+                                       CompletionCallback callback) {
+    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
+              title.build_id, name);
+
+    callback(true);
+    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::HexArrayToString(passphrase));
+}
+
+} // 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..2e9511f3f
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -0,0 +1,53 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Service::BCAT {
+
+using CompletionCallback = std::function<void(bool)>;
+using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
+using Passphrase = std::array<u8, 0x20>;
+
+struct TitleIDVersion {
+    u64 title_id;
+    u64 build_id;
+};
+
+class Backend {
+public:
+    explicit Backend(DirectoryGetter getter);
+    virtual ~Backend();
+
+    virtual bool Synchronize(TitleIDVersion title, CompletionCallback callback) = 0;
+    virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+                                      CompletionCallback callback) = 0;
+
+    virtual bool Clear(u64 title_id) = 0;
+
+    virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
+
+protected:
+    DirectoryGetter dir_getter;
+};
+
+class NullBackend : public Backend {
+public:
+    explicit NullBackend(const DirectoryGetter& getter);
+    ~NullBackend() override;
+
+    bool Synchronize(TitleIDVersion title, CompletionCallback callback) override;
+    bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+                              CompletionCallback callback) override;
+
+    bool Clear(u64 title_id) override;
+
+    void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
+};
+
+} // namespace Service::BCAT

From 2903f3524e7b9d802da4d23ae6d25d07f7eba8f5 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:47:58 -0400
Subject: [PATCH 09/31] bcat: Add BCAT backend for Boxcat service Downloads
 content from yuzu servers and unpacks it into the temporary directory
 provided. Fully supports all Backend features except passphrase.

---
 src/core/hle/service/bcat/backend/boxcat.cpp | 351 +++++++++++++++++++
 src/core/hle/service/bcat/backend/boxcat.h   |  56 +++
 2 files changed, 407 insertions(+)
 create mode 100644 src/core/hle/service/bcat/backend/boxcat.cpp
 create mode 100644 src/core/hle/service/bcat/backend/boxcat.h

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..539140f30
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -0,0 +1,351 @@
+// 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/lock.h"
+#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/bcat/backend/boxcat.h"
+#include "core/settings.h"
+
+namespace Service::BCAT {
+
+constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
+
+// Formatted using fmt with arg[0] = hex title id
+constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data";
+
+constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events";
+
+constexpr char BOXCAT_API_VERSION[] = "1";
+
+// HTTP status codes for Boxcat
+enum class ResponseStatus {
+    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;
+constexpr u64 VFS_COPY_BLOCK_SIZE = 1ull << 24; // 4MB
+
+namespace {
+
+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)], [] {});
+}
+
+} // namespace
+
+class Boxcat::Client {
+public:
+    Client(std::string zip_path, u64 title_id, u64 build_id)
+        : zip_path(std::move(zip_path)), title_id(title_id), build_id(build_id) {}
+
+    DownloadResult Download() {
+        const auto resolved_path = fmt::format(BOXCAT_PATHNAME_DATA, title_id);
+        if (client == nullptr) {
+            client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, TIMEOUT_SECONDS);
+        }
+
+        httplib::Headers headers{
+            {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)},
+            {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)},
+        };
+
+        if (FileUtil::Exists(zip_path)) {
+            FileUtil::IOFile file{zip_path, "rb"};
+            std::vector<u8> bytes(file.GetSize());
+            file.ReadBytes(bytes.data(), bytes.size());
+            const auto digest = DigestFile(bytes);
+            headers.insert({std::string("Boxcat-Current-Zip-Digest"),
+                            Common::HexArrayToString(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 >= 400)
+            return DownloadResult::GeneralWebError;
+
+        const auto content_type = response->headers.find("content-type");
+        if (content_type == response->headers.end() ||
+            content_type->second.find("application/zip") == std::string::npos) {
+            return DownloadResult::InvalidContentType;
+        }
+
+        FileUtil::CreateFullPath(zip_path);
+        FileUtil::IOFile file{zip_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;
+    }
+
+private:
+    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 zip_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,
+                         CompletionCallback callback, std::optional<std::string> dir_name = {}) {
+    const auto failure = [&callback] {
+        // Acquire the HLE mutex
+        std::lock_guard lock{HLE::g_hle_lock};
+        callback(false);
+    };
+
+    if (Settings::values.bcat_boxcat_local) {
+        LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
+        // Acquire the HLE mutex
+        std::lock_guard lock{HLE::g_hle_lock};
+        callback(true);
+        return;
+    }
+
+    const auto zip_path{GetZIPFilePath(title.title_id)};
+    Boxcat::Client client{zip_path, title.title_id, title.build_id};
+
+    const auto res = client.Download();
+    if (res != DownloadResult::Success) {
+        LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
+        HandleDownloadDisplayResult(res);
+        failure();
+        return;
+    }
+
+    FileUtil::IOFile zip{zip_path, "rb"};
+    const auto size = zip.GetSize();
+    std::vector<u8> bytes(size);
+    if (size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
+        LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
+        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!");
+        failure();
+        return;
+    }
+
+    if (dir_name == std::nullopt) {
+        const auto target_dir = dir_getter(title.title_id);
+        if (target_dir == nullptr ||
+            !FileSys::VfsRawCopyD(extracted, target_dir, VFS_COPY_BLOCK_SIZE)) {
+            LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+            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!");
+            failure();
+            return;
+        }
+
+        const auto target_sub = target_dir->GetSubdirectory(*dir_name);
+        const auto source_sub = extracted->GetSubdirectory(*dir_name);
+
+        if (target_sub == nullptr || source_sub == nullptr ||
+            !FileSys::VfsRawCopyD(source_sub, target_sub, VFS_COPY_BLOCK_SIZE)) {
+            LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
+            failure();
+            return;
+        }
+    }
+
+    // Acquire the HLE mutex
+    std::lock_guard lock{HLE::g_hle_lock};
+    callback(true);
+}
+
+bool Boxcat::Synchronize(TitleIDVersion title, CompletionCallback callback) {
+    is_syncing.exchange(true);
+    std::thread(&SynchronizeInternal, dir_getter, title, callback, std::nullopt).detach();
+    return true;
+}
+
+bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
+                                  CompletionCallback callback) {
+    is_syncing.exchange(true);
+    std::thread(&SynchronizeInternal, dir_getter, title, callback, 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::HexArrayToString(passphrase));
+}
+
+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("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)},
+    };
+
+    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& e) {
+        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..f4e60f264
--- /dev/null
+++ b/src/core/hle/service/bcat/backend/boxcat.h
@@ -0,0 +1,56 @@
+// 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,
+                                    CompletionCallback callback,
+                                    std::optional<std::string> dir_name);
+
+public:
+    explicit Boxcat(DirectoryGetter getter);
+    ~Boxcat() override;
+
+    bool Synchronize(TitleIDVersion title, CompletionCallback callback) override;
+    bool SynchronizeDirectory(TitleIDVersion title, std::string name,
+                              CompletionCallback callback) override;
+
+    bool Clear(u64 title_id) override;
+
+    void SetPassphrase(u64 title_id, const Passphrase& passphrase) 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

From 68658a8385b74454c8523efe95ceb81b34bb8812 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:49:46 -0400
Subject: [PATCH 10/31] module: Create BCAT backend based upon Settings value
 on construction

---
 src/core/CMakeLists.txt              | 20 ++++++++++++++++++++
 src/core/hle/service/bcat/module.cpp | 14 +++++++++++++-
 src/core/hle/service/bcat/module.h   |  3 +++
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a6b56c9c6..3416854db 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,3 +1,9 @@
+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
@@ -499,6 +510,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/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index b7bd738fc..32d3d5cfc 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -38,10 +38,22 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IBcatService>();
+namespace {
+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));
 }
+} // Anonymous namespace
 
 Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
-    : ServiceFramework(name), module(std::move(module)) {}
+    : ServiceFramework(name), module(std::move(module)),
+      backend(CreateBackendFromSettings(&Service::FileSystem::GetBCATDirectory)) {}
 
 Module::Interface::~Interface() = default;
 
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index f0d63cab0..4af363bfd 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -8,6 +8,8 @@
 
 namespace Service::BCAT {
 
+class Backend;
+
 class Module final {
 public:
     class Interface : public ServiceFramework<Interface> {
@@ -19,6 +21,7 @@ public:
 
     protected:
         std::shared_ptr<Module> module;
+        std::unique_ptr<Backend> backend;
     };
 };
 

From 78d146f907cbab60026f972e1be7cc8eb83e05bb Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:51:18 -0400
Subject: [PATCH 11/31] bcat: Add commands to create
 IDeliveryCacheStorageService Used to access contents of download.

---
 src/core/hle/service/bcat/bcat.cpp   |  4 ++++
 src/core/hle/service/bcat/module.cpp | 28 ++++++++++++++++++++++++++--
 src/core/hle/service/bcat/module.h   |  2 ++
 3 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 179aa4949..391f599ee 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -8,9 +8,13 @@ namespace Service::BCAT {
 
 BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
     : Module::Interface(std::move(module), 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/module.cpp b/src/core/hle/service/bcat/module.cpp
index 32d3d5cfc..fd742fde2 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -11,7 +11,8 @@ namespace Service::BCAT {
 
 class IBcatService final : public ServiceFramework<IBcatService> {
 public:
-    IBcatService() : ServiceFramework("IBcatService") {
+    IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) {
+        // clang-format off
         static const FunctionInfo functions[] = {
             {10100, nullptr, "RequestSyncDeliveryCache"},
             {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"},
@@ -28,6 +29,7 @@ public:
             {90201, nullptr, "ClearDeliveryCacheStorage"},
             {90300, nullptr, "GetPushNotificationLog"},
         };
+        // clang-format on
         RegisterHandlers(functions);
     }
 };
@@ -37,7 +39,29 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
-    rb.PushIpcInterface<IBcatService>();
+    rb.PushIpcInterface<IBcatService>(*backend);
+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>(
+        Service::FileSystem::GetBCATDirectory(Core::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>(
+        Service::FileSystem::GetBCATDirectory(title_id));
+}
+
 namespace {
 std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
     const auto backend = Settings::values.bcat_backend;
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index 4af363bfd..fc52574c2 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -18,6 +18,8 @@ public:
         ~Interface() override;
 
         void CreateBcatService(Kernel::HLERequestContext& ctx);
+        void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
+        void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
 
     protected:
         std::shared_ptr<Module> module;

From 862131ead9cf8e10e4ff220d01cb1be16533d208 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:53:03 -0400
Subject: [PATCH 12/31] bcat: Implement IDeliveryCacheStorageService commands
 Used to create subclasses to manage files and directories and to list
 directories.

---
 src/core/hle/service/bcat/module.cpp | 58 ++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index fd742fde2..2d8341359 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -40,6 +40,64 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IBcatService>(*backend);
+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(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<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");
 

From 8812018c1defbd76b9a44aaf8e050bf97c73aeae Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:53:39 -0400
Subject: [PATCH 13/31] bcat: Implement IDeliveryCacheDirectoryService commands
 Used to list and get directories at the root level.

---
 src/core/hle/service/bcat/module.cpp | 99 ++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 2d8341359..6645599f2 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -40,6 +40,105 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IBcatService>(*backend);
+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(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<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<u32>(files.size());
+    }
+
+    FileSys::VirtualDir root;
+    FileSys::VirtualDir current_dir;
+};
+
 class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
 public:
     IDeliveryCacheStorageService(FileSys::VirtualDir root_)

From f352ad5c93aa0266cba8b4c15ee3f0c5f0b0aed2 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:54:26 -0400
Subject: [PATCH 14/31] bcat: Implement IDeliveryCacheFileService commands Used
 to read the contents of files and access their metadata.

---
 src/core/hle/service/bcat/module.cpp | 117 +++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 6645599f2..25f68ed63 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -40,6 +40,123 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IBcatService>(*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(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:

From cb7c96b96a0ad781d5bb4387072d5d0becd74dd7 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:55:56 -0400
Subject: [PATCH 15/31] bcat: Implement IDeliveryCacheProgressService commands
 Used to query completion status and events for the current delivery task.

---
 src/core/hle/service/bcat/module.cpp | 131 +++++++++++++++++++++++++++
 1 file changed, 131 insertions(+)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 25f68ed63..1459fab11 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -2,13 +2,144 @@
 // 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>;
+
+struct DeliveryCacheProgressImpl {
+    enum class Status : u8 {
+        Incomplete = 0x1,
+        Complete = 0x9,
+    };
+
+    Status status = Status::Incomplete;
+    INSERT_PADDING_BYTES(
+        0x1FF); ///< TODO(DarkLordZach): RE this structure. It just seems to convey info about the
+                ///< progress of the BCAT sync, but for us just setting completion works.
+};
+static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
+              "DeliveryCacheProgressImpl has incorrect size.");
+
+namespace {
+
+u64 GetCurrentBuildID() {
+    const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID();
+    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, std::array<char, 0x20> name) {
+    return VerifyNameValidInternal(ctx, name, '-');
+}
+
+bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) {
+    return VerifyNameValidInternal(ctx, name, '.');
+}
+
+} // Anonymous namespace
+
+using DirectoryName = std::array<char, 0x20>;
+using FileName = std::array<char, 0x20>;
+
+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(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) {

From 86773a7f081a8a6c71643ecdc6573b65dbf0ccd3 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:57:37 -0400
Subject: [PATCH 16/31] bcat: Implement cmd RequestSyncDeliveryCache and
 variant Variant also supports only updating a single directory. These just
 both invoke backend commands.

---
 src/core/hle/service/bcat/module.cpp | 72 +++++++++++++++++++++++++++-
 1 file changed, 70 insertions(+), 2 deletions(-)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 1459fab11..605aa6e00 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -145,8 +145,8 @@ public:
     IBcatService(Backend& backend) : ServiceFramework("IBcatService"), 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"},
@@ -162,7 +162,74 @@ public:
         };
         // clang-format on
         RegisterHandlers(functions);
+
+        auto& kernel{Core::System::GetInstance().Kernel()};
+        progress.at(static_cast<std::size_t>(SyncType::Normal)).event =
+            Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
+                                                   "BCAT::IDeliveryCacheProgressEvent");
+        progress.at(static_cast<std::size_t>(SyncType::Directory)).event =
+            Kernel::WritableEvent::CreateEventPair(
+                kernel, Kernel::ResetType::OneShot,
+                "BCAT::IDeliveryCacheProgressEvent::DirectoryName");
     }
+
+private:
+    enum class SyncType {
+        Normal,
+        Directory,
+        Count,
+    };
+
+    std::function<void(bool)> CreateCallback(SyncType type) {
+        return [this, type](bool success) {
+            auto& pair{progress.at(static_cast<std::size_t>(type))};
+            pair.impl.status = DeliveryCacheProgressImpl::Status::Complete;
+            pair.event.writable->Signal();
+        };
+    }
+
+    std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
+        const auto& pair{progress.at(static_cast<std::size_t>(type))};
+        return std::make_shared<IDeliveryCacheProgressService>(pair.event.readable, pair.impl);
+    }
+
+    void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_BCAT, "called");
+
+        backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
+                            CreateCallback(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({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
+                                     name, CreateCallback(SyncType::Directory));
+
+        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+        rb.Push(RESULT_SUCCESS);
+        rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
+    }
+
+    }
+
+    Backend& backend;
+
+    struct ProgressPair {
+        Kernel::EventPair event;
+        DeliveryCacheProgressImpl impl;
+    };
+
+    std::array<ProgressPair, static_cast<std::size_t>(SyncType::Count)> progress{};
 };
 
 void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
@@ -171,6 +238,7 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IBcatService>(*backend);
+}
 
 class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
 public:

From 1bde5a3c6a205de445b2086f2fda2a830d70b8f6 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 18:59:35 -0400
Subject: [PATCH 17/31] bcat: Implement cmd 30100 SetPassphrase Takes a title
 ID and passphrase (0x40 byte string) and passes it to the backend.

---
 src/core/hle/service/bcat/module.cpp | 34 +++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 605aa6e00..cbda8e0d3 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -150,7 +150,7 @@ public:
             {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"},
@@ -220,6 +220,38 @@ private:
         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::HexVectorToString(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);
+    }
+
     }
 
     Backend& backend;

From 102db206e0cf8c0332e6ec0c2c6f4fa8c7d4f05c Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 19:00:36 -0400
Subject: [PATCH 18/31] bcat: Implement cmd 90201 ClearDeliveryCacheStorage
 Takes a title ID and simply deletes all the data for that title ID's bcat.
 Invokes the respective backend command.

---
 src/core/hle/service/bcat/module.cpp | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index cbda8e0d3..9244c265a 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -157,7 +157,7 @@ public:
             {30203, nullptr, "UnblockDeliveryTask"},
             {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
             {90200, nullptr, "GetDeliveryList"},
-            {90201, nullptr, "ClearDeliveryCacheStorage"},
+            {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
             {90300, nullptr, "GetPushNotificationLog"},
         };
         // clang-format on
@@ -252,6 +252,28 @@ private:
         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);
     }
 
     Backend& backend;

From f0551aef0912c02e273021fd5186f49283ebcb14 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 19:01:23 -0400
Subject: [PATCH 19/31] yuzu: Add UI tab to configure BCAT services Also
 displays current events if boxcat is selected.

---
 src/yuzu/CMakeLists.txt                      |   6 +
 src/yuzu/configuration/configure.ui          |  11 ++
 src/yuzu/configuration/configure_dialog.cpp  |   1 +
 src/yuzu/configuration/configure_service.cpp | 129 +++++++++++++++++++
 src/yuzu/configuration/configure_service.h   |  34 +++++
 src/yuzu/configuration/configure_service.ui  | 121 +++++++++++++++++
 6 files changed, 302 insertions(+)
 create mode 100644 src/yuzu/configuration/configure_service.cpp
 create mode 100644 src/yuzu/configuration/configure_service.h
 create mode 100644 src/yuzu/configuration/configure_service.ui

diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index dc6fa07fc..fffb20220 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -66,6 +66,8 @@ 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_system.cpp
     configuration/configure_system.h
     configuration/configure_system.ui
@@ -186,6 +188,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/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..520b7e193 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();
 }
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
new file mode 100644
index 000000000..fca785d0e
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -0,0 +1,129 @@
+// 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>()), watcher(this) {
+    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() {
+    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::Offline:
+        return {"", tr("The boxcat service is offline or you are not connected to the internet.")};
+    case Service::BCAT::Boxcat::StatusResult::ParseError:
+        return {"",
+                tr("There was an error while processing the boxcat event data. Contact the yuzu "
+                   "developers.")};
+    case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
+        return {"",
+                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;
+    for (const auto& [key, value] : map) {
+        out += QStringLiteral("%1<b>%2</b><br>%3")
+                   .arg(out.isEmpty() ? "" : "<br>")
+                   .arg(QString::fromStdString(key))
+                   .arg(FormatEventStatusString(value));
+    }
+    return {QStringLiteral("Current Boxcat Events"), 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("");
+    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..ee50d5a79
--- /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;
+};
diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui
new file mode 100644
index 000000000..f5b683d25
--- /dev/null
+++ b/src/yuzu/configuration/configure_service.ui
@@ -0,0 +1,121 @@
+<?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>
+         </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>

From d8bcb1e9739e5c672d62df01a013812e082eefb5 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 19:01:58 -0400
Subject: [PATCH 20/31] cmake: Add cmake option to build Boxcat backend Default
 enabled

---
 CMakeLists.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bfa104034..9b3b0d6d5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,8 @@ option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
 
 option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
 
+option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON)
+
 option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
 
 option(ENABLE_VULKAN "Enables Vulkan backend" ON)

From 02f8f1bb3e424c029d3074da0e7586f5c05f5131 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 28 Apr 2019 21:00:14 -0400
Subject: [PATCH 21/31] configure_service: Allow Qt to open external links

---
 src/yuzu/configuration/configure_service.ui | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui
index f5b683d25..9668dd557 100644
--- a/src/yuzu/configuration/configure_service.ui
+++ b/src/yuzu/configuration/configure_service.ui
@@ -78,6 +78,9 @@
           <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">

From fe8c7e66e291b1fb3bef206e0d5809ad80441e9b Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Wed, 1 May 2019 22:40:51 -0400
Subject: [PATCH 22/31] am: Unstub PopLaunchParameter and add bcat connection
 for app-specific data Previously we were simply returning the
 account-preselect structure all times but if passed with a different mode the
 game expects application-specific data. This also adds a hook for BCAT into
 this allowing us to send the launch parameter through bcat,

---
 src/core/hle/service/am/am.cpp | 66 +++++++++++++++++++++++++---------
 src/core/hle/service/am/am.h   |  2 ++
 2 files changed, 52 insertions(+), 16 deletions(-)

diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 797c9a06f..79f9a393e 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,
+};
 
-struct LaunchParameters {
+constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA;
+
+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,54 @@ 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(&FileSystem::GetBCATDirectory);
+        const auto build_id_full = Core::System::GetInstance().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({Core::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(
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index a3baeb673..9169eb2bd 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -255,6 +255,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;
 };

From ea17b294ea04a00d94025cda97b2cd4d5f730b17 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Wed, 1 May 2019 22:41:32 -0400
Subject: [PATCH 23/31] bcat: Expose CreateBackendFromSettings helper function

---
 src/core/hle/service/bcat/backend/backend.h | 2 ++
 src/core/hle/service/bcat/module.cpp        | 2 --
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 2e9511f3f..e412819e1 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -50,4 +50,6 @@ public:
     void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
 };
 
+std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
+
 } // namespace Service::BCAT
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index 9244c265a..a8d545992 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -589,7 +589,6 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
         Service::FileSystem::GetBCATDirectory(title_id));
 }
 
-namespace {
 std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
     const auto backend = Settings::values.bcat_backend;
 
@@ -600,7 +599,6 @@ std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
 
     return std::make_unique<NullBackend>(std::move(getter));
 }
-} // Anonymous namespace
 
 Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
     : ServiceFramework(name), module(std::move(module)),

From b8ce87103d1aa382a9b96f3379a599f869b02a68 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Wed, 1 May 2019 22:42:17 -0400
Subject: [PATCH 24/31] bcat: Add backend function for BCAT Indirect (launch
 parameter) Returns the data that should be returned by PopLaunchParameter
 kind=ApplicationSpecific.

---
 src/core/hle/service/bcat/backend/backend.cpp | 6 ++++++
 src/core/hle/service/bcat/backend/backend.h   | 5 +++++
 2 files changed, 11 insertions(+)

diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index aefa2208d..9a67da2ef 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -44,4 +44,10 @@ void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
               Common::HexArrayToString(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
index e412819e1..5b4118814 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <functional>
+#include <optional>
 #include "common/common_types.h"
 #include "core/file_sys/vfs_types.h"
 
@@ -32,6 +33,8 @@ public:
 
     virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
 
+    virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
+
 protected:
     DirectoryGetter dir_getter;
 };
@@ -48,6 +51,8 @@ public:
     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);

From e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Wed, 1 May 2019 22:42:50 -0400
Subject: [PATCH 25/31] boxcat: Add downloading and client for launch parameter
 data

---
 src/core/hle/service/bcat/backend/boxcat.cpp | 91 ++++++++++++++++----
 src/core/hle/service/bcat/backend/boxcat.h   |  2 +
 2 files changed, 77 insertions(+), 16 deletions(-)

diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 539140f30..f37f92bf4 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -25,13 +25,16 @@ constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
 
 // Formatted using fmt with arg[0] = hex title id
 constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data";
+constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/boxcat/titles/{:016X}/launchparam";
 
 constexpr char BOXCAT_PATHNAME_EVENTS[] = "/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.
@@ -74,6 +77,11 @@ 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);
@@ -98,27 +106,40 @@ void HandleDownloadDisplayResult(DownloadResult res) {
 
 class Boxcat::Client {
 public:
-    Client(std::string zip_path, u64 title_id, u64 build_id)
-        : zip_path(std::move(zip_path)), title_id(title_id), build_id(build_id) {}
+    Client(std::string path, u64 title_id, u64 build_id)
+        : path(std::move(path)), title_id(title_id), build_id(build_id) {}
 
-    DownloadResult Download() {
-        const auto resolved_path = fmt::format(BOXCAT_PATHNAME_DATA, title_id);
+    DownloadResult DownloadDataZip() {
+        return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
+                                "Boxcat-Data-Digest", "application/zip");
+    }
+
+    DownloadResult DownloadLaunchParam() {
+        return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
+                                TIMEOUT_SECONDS / 3, "Boxcat-LaunchParam-Digest",
+                                "application/octet-stream");
+    }
+
+private:
+    DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
+                                    const std::string& digest_header_name,
+                                    const std::string& content_type_name) {
         if (client == nullptr) {
-            client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, TIMEOUT_SECONDS);
+            client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
         }
 
         httplib::Headers headers{
             {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)},
+            {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
             {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)},
         };
 
-        if (FileUtil::Exists(zip_path)) {
-            FileUtil::IOFile file{zip_path, "rb"};
+        if (FileUtil::Exists(path)) {
+            FileUtil::IOFile file{path, "rb"};
             std::vector<u8> bytes(file.GetSize());
             file.ReadBytes(bytes.data(), bytes.size());
             const auto digest = DigestFile(bytes);
-            headers.insert({std::string("Boxcat-Current-Zip-Digest"),
-                            Common::HexArrayToString(digest, false)});
+            headers.insert({digest_header_name, Common::HexArrayToString(digest, false)});
         }
 
         const auto response = client->Get(resolved_path.c_str(), headers);
@@ -133,17 +154,17 @@ public:
             return DownloadResult::NoMatchTitleId;
         if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
             return DownloadResult::NoMatchBuildId;
-        if (response->status >= 400)
+        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("application/zip") == std::string::npos) {
+            content_type->second.find(content_type_name) == std::string::npos) {
             return DownloadResult::InvalidContentType;
         }
 
-        FileUtil::CreateFullPath(zip_path);
-        FileUtil::IOFile file{zip_path, "wb"};
+        FileUtil::CreateFullPath(path);
+        FileUtil::IOFile file{path, "wb"};
         if (!file.IsOpen())
             return DownloadResult::GeneralFSError;
         if (!file.Resize(response->body.size()))
@@ -154,7 +175,6 @@ public:
         return DownloadResult::Success;
     }
 
-private:
     using Digest = std::array<u8, 0x20>;
     static Digest DigestFile(std::vector<u8> bytes) {
         Digest out{};
@@ -163,7 +183,7 @@ private:
     }
 
     std::unique_ptr<httplib::Client> client;
-    std::string zip_path;
+    std::string path;
     u64 title_id;
     u64 build_id;
 };
@@ -191,9 +211,14 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
     const auto zip_path{GetZIPFilePath(title.title_id)};
     Boxcat::Client client{zip_path, title.title_id, title.build_id};
 
-    const auto res = client.Download();
+    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);
         failure();
         return;
@@ -286,6 +311,39 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
               Common::HexArrayToString(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 (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),
@@ -293,6 +351,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
 
     httplib::Headers headers{
         {std::string("Boxcat-Client-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);
diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h
index f4e60f264..1148a4eca 100644
--- a/src/core/hle/service/bcat/backend/boxcat.h
+++ b/src/core/hle/service/bcat/backend/boxcat.h
@@ -36,6 +36,8 @@ public:
 
     void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
 
+    std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
+
     enum class StatusResult {
         Success,
         Offline,

From 92b70a3bf9d4657dccc5a5f2cffdd7789946ca14 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 6 May 2019 18:47:27 -0400
Subject: [PATCH 26/31] boxcat: Use Etag header names for file digest

---
 src/core/file_sys/vfs_libzip.cpp             | 24 ++++++++------------
 src/core/hle/service/bcat/backend/boxcat.cpp | 21 +++++++++--------
 2 files changed, 21 insertions(+), 24 deletions(-)

diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index 64f19a0ea..e34474ae0 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -15,25 +15,25 @@ VirtualDir ExtractZIP(VirtualFile file) {
     zip_error_t error{};
 
     const auto data = file->ReadAllBytes();
-    const auto src = zip_source_buffer_create(data.data(), data.size(), 0, &error);
+    std::unique_ptr<zip_source_t, decltype(&zip_source_free)> src{
+        zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_free};
     if (src == nullptr)
         return nullptr;
 
-    const auto zip = zip_open_from_source(src, 0, &error);
+    std::unique_ptr<zip_t, decltype(&zip_discard)> zip{zip_open_from_source(src.get(), 0, &error),
+                                                       zip_discard};
     if (zip == nullptr)
         return nullptr;
 
     std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>();
 
-    const auto num_entries = zip_get_num_entries(zip, 0);
-    if (num_entries == -1)
-        return nullptr;
+    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, i, 0, &stat);
+        const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat);
         if (stat_res == -1)
             return nullptr;
 
@@ -41,15 +41,14 @@ VirtualDir ExtractZIP(VirtualFile file) {
         if (name.empty())
             continue;
 
-        if (name[name.size() - 1] != '/') {
-            const auto file = zip_fopen_index(zip, i, 0);
+        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, buf.data(), buf.size()) != buf.size())
+            if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size())
                 return nullptr;
 
-            zip_fclose(file);
-
             const auto parts = FileUtil::SplitPathComponents(stat.name);
             const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back());
 
@@ -74,9 +73,6 @@ VirtualDir ExtractZIP(VirtualFile file) {
         }
     }
 
-    zip_source_close(src);
-    zip_close(zip);
-
     return out;
 }
 
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index f37f92bf4..31d2e045c 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -111,18 +111,16 @@ public:
 
     DownloadResult DownloadDataZip() {
         return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
-                                "Boxcat-Data-Digest", "application/zip");
+                                "application/zip");
     }
 
     DownloadResult DownloadLaunchParam() {
         return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
-                                TIMEOUT_SECONDS / 3, "Boxcat-LaunchParam-Digest",
-                                "application/octet-stream");
+                                TIMEOUT_SECONDS / 3, "application/octet-stream");
     }
 
 private:
     DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
-                                    const std::string& digest_header_name,
                                     const std::string& content_type_name) {
         if (client == nullptr) {
             client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
@@ -136,10 +134,13 @@ private:
 
         if (FileUtil::Exists(path)) {
             FileUtil::IOFile file{path, "rb"};
-            std::vector<u8> bytes(file.GetSize());
-            file.ReadBytes(bytes.data(), bytes.size());
-            const auto digest = DigestFile(bytes);
-            headers.insert({digest_header_name, Common::HexArrayToString(digest, false)});
+            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::HexArrayToString(digest, false)});
+            }
         }
 
         const auto response = client->Get(resolved_path.c_str(), headers);
@@ -227,7 +228,7 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
     FileUtil::IOFile zip{zip_path, "rb"};
     const auto size = zip.GetSize();
     std::vector<u8> bytes(size);
-    if (size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != 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);
         failure();
         return;
@@ -335,7 +336,7 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
     FileUtil::IOFile bin{path, "rb"};
     const auto size = bin.GetSize();
     std::vector<u8> bytes(size);
-    if (size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != 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;

From 2d410ddf4d9c0109d64fdf3319efeb9e6cc0bce1 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 13 May 2019 18:51:02 -0400
Subject: [PATCH 27/31] bcat: Implement DeliveryCacheProgressImpl structure

Huge thanks to lioncash for re-ing this for me.
---
 src/core/file_sys/vfs_libzip.cpp              |   8 +-
 src/core/hle/service/bcat/backend/backend.cpp |  88 ++++++++++-
 src/core/hle/service/bcat/backend/backend.h   |  97 +++++++++++-
 src/core/hle/service/bcat/backend/boxcat.cpp  | 147 ++++++++++++++----
 src/core/hle/service/bcat/backend/boxcat.h    |   6 +-
 src/core/hle/service/bcat/module.cpp          |  56 ++-----
 6 files changed, 314 insertions(+), 88 deletions(-)

diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index e34474ae0..8bdaa7e4a 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -15,13 +15,13 @@ VirtualDir ExtractZIP(VirtualFile file) {
     zip_error_t error{};
 
     const auto data = file->ReadAllBytes();
-    std::unique_ptr<zip_source_t, decltype(&zip_source_free)> src{
-        zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_free};
+    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_discard)> zip{zip_open_from_source(src.get(), 0, &error),
-                                                       zip_discard};
+    std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error),
+                                                     zip_close};
     if (zip == nullptr)
         return nullptr;
 
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index 9a67da2ef..e389ad568 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -4,10 +4,90 @@
 
 #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 event_name) : impl{} {
+    auto& kernel{Core::System::GetInstance().Kernel()};
+    event = Kernel::WritableEvent::CreateEventPair(
+        kernel, Kernel::ResetType::OneShot, "ProgressServiceBackend:UpdateEvent:" + event_name);
+}
+
+Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
+    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(dir_name.size(), 0x31ull));
+    std::memcpy(impl.current_file.data(), file_name.data(), std::min(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(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;
@@ -16,20 +96,20 @@ NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(gett
 
 NullBackend::~NullBackend() = default;
 
-bool NullBackend::Synchronize(TitleIDVersion title, CompletionCallback callback) {
+bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
     LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
               title.build_id);
 
-    callback(true);
+    progress.FinishDownload(RESULT_SUCCESS);
     return true;
 }
 
 bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
-                                       CompletionCallback callback) {
+                                       ProgressServiceBackend& progress) {
     LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
               title.build_id, name);
 
-    callback(true);
+    progress.FinishDownload(RESULT_SUCCESS);
     return true;
 }
 
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 5b4118814..50973a13a 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -8,10 +8,14 @@
 #include <optional>
 #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 {
 
-using CompletionCallback = std::function<void(bool)>;
+struct DeliveryCacheProgressImpl;
+
 using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
 using Passphrase = std::array<u8, 0x20>;
 
@@ -20,33 +24,116 @@ struct TitleIDVersion {
     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;
+
+    ProgressServiceBackend(std::string event_name);
+
+    Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
+    DeliveryCacheProgressImpl& GetImpl();
+
+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:
+    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();
 
-    virtual bool Synchronize(TitleIDVersion title, CompletionCallback callback) = 0;
+    // 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,
-                                      CompletionCallback callback) = 0;
+                                      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(const DirectoryGetter& getter);
     ~NullBackend() override;
 
-    bool Synchronize(TitleIDVersion title, CompletionCallback callback) override;
+    bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
     bool SynchronizeDirectory(TitleIDVersion title, std::string name,
-                              CompletionCallback callback) override;
+                              ProgressServiceBackend& progress) override;
 
     bool Clear(u64 title_id) override;
 
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 31d2e045c..3754594df 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -14,13 +14,28 @@
 #include "core/file_sys/vfs_libzip.h"
 #include "core/file_sys/vfs_vector.h"
 #include "core/frontend/applets/error.h"
-#include "core/hle/lock.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
@@ -102,7 +117,68 @@ void HandleDownloadDisplayResult(DownloadResult res) {
         DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
 }
 
-} // namespace
+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:
@@ -194,24 +270,24 @@ Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
 Boxcat::~Boxcat() = default;
 
 void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
-                         CompletionCallback callback, std::optional<std::string> dir_name = {}) {
-    const auto failure = [&callback] {
-        // Acquire the HLE mutex
-        std::lock_guard lock{HLE::g_hle_lock};
-        callback(false);
-    };
+                         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.");
-        // Acquire the HLE mutex
-        std::lock_guard lock{HLE::g_hle_lock};
-        callback(true);
+        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);
@@ -221,68 +297,85 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
         }
 
         HandleDownloadDisplayResult(res);
-        failure();
+        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);
-        failure();
+        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!");
-        failure();
+        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 ||
-            !FileSys::VfsRawCopyD(extracted, target_dir, VFS_COPY_BLOCK_SIZE)) {
+        if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
             LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
-            failure();
+            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!");
-            failure();
+            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 ||
-            !FileSys::VfsRawCopyD(source_sub, target_sub, VFS_COPY_BLOCK_SIZE)) {
+            !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
             LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
-            failure();
+            progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
             return;
         }
     }
 
-    // Acquire the HLE mutex
-    std::lock_guard lock{HLE::g_hle_lock};
-    callback(true);
+    progress.FinishDownload(RESULT_SUCCESS);
 }
 
-bool Boxcat::Synchronize(TitleIDVersion title, CompletionCallback callback) {
+bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
     is_syncing.exchange(true);
-    std::thread(&SynchronizeInternal, dir_getter, title, callback, std::nullopt).detach();
+    std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
+        .detach();
     return true;
 }
 
 bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
-                                  CompletionCallback callback) {
+                                  ProgressServiceBackend& progress) {
     is_syncing.exchange(true);
-    std::thread(&SynchronizeInternal, dir_getter, title, callback, name).detach();
+    std::thread(
+        [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
+        .detach();
     return true;
 }
 
diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h
index 1148a4eca..601151189 100644
--- a/src/core/hle/service/bcat/backend/boxcat.h
+++ b/src/core/hle/service/bcat/backend/boxcat.h
@@ -21,16 +21,16 @@ struct EventStatus {
 /// 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,
-                                    CompletionCallback callback,
+                                    ProgressServiceBackend& progress,
                                     std::optional<std::string> dir_name);
 
 public:
     explicit Boxcat(DirectoryGetter getter);
     ~Boxcat() override;
 
-    bool Synchronize(TitleIDVersion title, CompletionCallback callback) override;
+    bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
     bool SynchronizeDirectory(TitleIDVersion title, std::string name,
-                              CompletionCallback callback) override;
+                              ProgressServiceBackend& progress) override;
 
     bool Clear(u64 title_id) override;
 
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index a8d545992..d5f9e9d3b 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -33,20 +33,6 @@ constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
 
 using BCATDigest = std::array<u8, 0x10>;
 
-struct DeliveryCacheProgressImpl {
-    enum class Status : u8 {
-        Incomplete = 0x1,
-        Complete = 0x9,
-    };
-
-    Status status = Status::Incomplete;
-    INSERT_PADDING_BYTES(
-        0x1FF); ///< TODO(DarkLordZach): RE this structure. It just seems to convey info about the
-                ///< progress of the BCAT sync, but for us just setting completion works.
-};
-static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
-              "DeliveryCacheProgressImpl has incorrect size.");
-
 namespace {
 
 u64 GetCurrentBuildID() {
@@ -84,19 +70,16 @@ bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x
     return true;
 }
 
-bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) {
+bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
     return VerifyNameValidInternal(ctx, name, '-');
 }
 
-bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) {
+bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
     return VerifyNameValidInternal(ctx, name, '.');
 }
 
 } // Anonymous namespace
 
-using DirectoryName = std::array<char, 0x20>;
-using FileName = std::array<char, 0x20>;
-
 struct DeliveryCacheDirectoryEntry {
     FileName name;
     u64 size;
@@ -162,15 +145,6 @@ public:
         };
         // clang-format on
         RegisterHandlers(functions);
-
-        auto& kernel{Core::System::GetInstance().Kernel()};
-        progress.at(static_cast<std::size_t>(SyncType::Normal)).event =
-            Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
-                                                   "BCAT::IDeliveryCacheProgressEvent");
-        progress.at(static_cast<std::size_t>(SyncType::Directory)).event =
-            Kernel::WritableEvent::CreateEventPair(
-                kernel, Kernel::ResetType::OneShot,
-                "BCAT::IDeliveryCacheProgressEvent::DirectoryName");
     }
 
 private:
@@ -180,24 +154,17 @@ private:
         Count,
     };
 
-    std::function<void(bool)> CreateCallback(SyncType type) {
-        return [this, type](bool success) {
-            auto& pair{progress.at(static_cast<std::size_t>(type))};
-            pair.impl.status = DeliveryCacheProgressImpl::Status::Complete;
-            pair.event.writable->Signal();
-        };
-    }
-
     std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
-        const auto& pair{progress.at(static_cast<std::size_t>(type))};
-        return std::make_shared<IDeliveryCacheProgressService>(pair.event.readable, pair.impl);
+        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({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
-                            CreateCallback(SyncType::Normal));
+                            progress.at(static_cast<std::size_t>(SyncType::Normal)));
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(RESULT_SUCCESS);
@@ -213,7 +180,8 @@ private:
         LOG_DEBUG(Service_BCAT, "called, name={}", name);
 
         backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
-                                     name, CreateCallback(SyncType::Directory));
+                                     name,
+                                     progress.at(static_cast<std::size_t>(SyncType::Directory)));
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(RESULT_SUCCESS);
@@ -278,12 +246,10 @@ private:
 
     Backend& backend;
 
-    struct ProgressPair {
-        Kernel::EventPair event;
-        DeliveryCacheProgressImpl impl;
+    std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
+        ProgressServiceBackend{"Normal"},
+        ProgressServiceBackend{"Directory"},
     };
-
-    std::array<ProgressPair, static_cast<std::size_t>(SyncType::Count)> progress{};
 };
 
 void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {

From bcf1eafb8bd1a810fd33a7e7e06a86173b4bfb9f Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Thu, 20 Jun 2019 20:31:17 -0400
Subject: [PATCH 28/31] boxcat: Implement events global field

---
 externals/libzip                              |  2 +-
 src/core/hle/service/bcat/backend/backend.cpp | 13 ++++++----
 src/core/hle/service/bcat/backend/boxcat.cpp  |  5 ++--
 src/core/hle/service/bcat/module.cpp          |  8 +++----
 src/yuzu/configuration/config.cpp             | 17 ++++++++-----
 src/yuzu/configuration/configure_service.cpp  | 24 ++++++++++++-------
 src/yuzu/configuration/configure_service.h    |  6 ++---
 7 files changed, 44 insertions(+), 31 deletions(-)

diff --git a/externals/libzip b/externals/libzip
index bebbb54c8..bd7a8103e 160000
--- a/externals/libzip
+++ b/externals/libzip
@@ -1 +1 @@
-Subproject commit bebbb54c8e691f019415fcb852ef4d53ebbc5000
+Subproject commit bd7a8103e96bc6d50164447f6b7b57bb786d8e2a
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index e389ad568..9b677debe 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -13,7 +13,7 @@ namespace Service::BCAT {
 ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} {
     auto& kernel{Core::System::GetInstance().Kernel()};
     event = Kernel::WritableEvent::CreateEventPair(
-        kernel, Kernel::ResetType::OneShot, "ProgressServiceBackend:UpdateEvent:" + event_name);
+        kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name);
 }
 
 Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
@@ -48,8 +48,10 @@ void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
     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(dir_name.size(), 0x31ull));
-    std::memcpy(impl.current_file.data(), file_name.data(), std::min(file_name.size(), 0x31ull));
+    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();
 }
 
@@ -68,7 +70,8 @@ void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
     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(dir_name.size(), 0x31ull));
+    std::memcpy(impl.current_directory.data(), dir_name.data(),
+                std::min<u64>(dir_name.size(), 0x31ull));
     SignalUpdate();
 }
 
@@ -121,7 +124,7 @@ bool NullBackend::Clear(u64 title_id) {
 
 void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
     LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
-              Common::HexArrayToString(passphrase));
+              Common::HexToString(passphrase));
 }
 
 std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 3754594df..5bc2e22d7 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -214,8 +214,7 @@ private:
                 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::HexArrayToString(digest, false)});
+                headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
             }
         }
 
@@ -402,7 +401,7 @@ bool Boxcat::Clear(u64 title_id) {
 
 void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
     LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
-              Common::HexArrayToString(passphrase));
+              Common::HexToString(passphrase));
 }
 
 std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index d5f9e9d3b..1b9a75a1c 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -195,7 +195,7 @@ private:
         const auto passphrase_raw = ctx.ReadBuffer();
 
         LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
-                  Common::HexVectorToString(passphrase_raw));
+                  Common::HexToString(passphrase_raw));
 
         if (title_id == 0) {
             LOG_ERROR(Service_BCAT, "Invalid title ID!");
@@ -335,7 +335,7 @@ private:
             rb.Push(ERROR_NO_OPEN_ENTITY);
         }
 
-        size = std::min(current_file->GetSize() - offset, size);
+        size = std::min<u64>(current_file->GetSize() - offset, size);
         const auto buffer = current_file->ReadBytes(size, offset);
         ctx.WriteBuffer(buffer);
 
@@ -437,7 +437,7 @@ private:
         }
 
         const auto files = current_dir->GetFiles();
-        write_size = std::min(write_size, files.size());
+        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) {
@@ -519,7 +519,7 @@ private:
 
         LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
 
-        size = std::min(size, entries.size() - next_read_index);
+        size = std::min<u64>(size, entries.size() - next_read_index);
         ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
         next_read_index += size;
 
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index ac7a77365..4cb27ddb2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -526,9 +526,13 @@ void Config::ReadDebuggingValues() {
 }
 
 void Config::ReadServiceValues() {
-    qt_config->beginGroup("Services");
-    Settings::values.bcat_backend = ReadSetting("bcat_backend", "boxcat").toString().toStdString();
-    Settings::values.bcat_boxcat_local = ReadSetting("bcat_boxcat_local", false).toBool();
+    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();
 }
 
@@ -973,9 +977,10 @@ void Config::SaveDebuggingValues() {
 }
 
 void Config::SaveServiceValues() {
-    qt_config->beginGroup("Services");
-    WriteSetting("bcat_backend", QString::fromStdString(Settings::values.bcat_backend), "null");
-    WriteSetting("bcat_boxcat_local", Settings::values.bcat_boxcat_local, false);
+    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();
 }
 
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
index fca785d0e..86160b479 100644
--- a/src/yuzu/configuration/configure_service.cpp
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -48,20 +48,20 @@ ConfigureService::ConfigureService(QWidget* parent)
     connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
             &ConfigureService::OnBCATImplChanged);
 
-    this->setConfiguration();
+    this->SetConfiguration();
 }
 
 ConfigureService::~ConfigureService() = default;
 
-void ConfigureService::applyConfiguration() {
+void ConfigureService::ApplyConfiguration() {
     Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
 }
 
-void ConfigureService::retranslateUi() {
+void ConfigureService::RetranslateUi() {
     ui->retranslateUi(this);
 }
 
-void ConfigureService::setConfiguration() {
+void ConfigureService::SetConfiguration() {
     int index = ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
     ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
 }
@@ -73,13 +73,14 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
 
     switch (res) {
     case Service::BCAT::Boxcat::StatusResult::Offline:
-        return {"", tr("The boxcat service is offline or you are not connected to the internet.")};
+        return {QStringLiteral(""),
+                tr("The boxcat service is offline or you are not connected to the internet.")};
     case Service::BCAT::Boxcat::StatusResult::ParseError:
-        return {"",
+        return {QStringLiteral(""),
                 tr("There was an error while processing the boxcat event data. Contact the yuzu "
                    "developers.")};
     case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
-        return {"",
+        return {QStringLiteral(""),
                 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.")};
     }
@@ -90,9 +91,14 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
     }
 
     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() ? "" : "<br>")
+                   .arg(out.isEmpty() ? QStringLiteral("") : QStringLiteral("<br>"))
                    .arg(QString::fromStdString(key))
                    .arg(FormatEventStatusString(value));
     }
@@ -104,7 +110,7 @@ void ConfigureService::OnBCATImplChanged() {
     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("");
+    ui->bcat_empty_header->setText(QStringLiteral(""));
     ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
 
     if (!boxcat)
diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_service.h
index ee50d5a79..efc8e21a8 100644
--- a/src/yuzu/configuration/configure_service.h
+++ b/src/yuzu/configuration/configure_service.h
@@ -19,11 +19,11 @@ public:
     explicit ConfigureService(QWidget* parent = nullptr);
     ~ConfigureService() override;
 
-    void applyConfiguration();
-    void retranslateUi();
+    void ApplyConfiguration();
+    void RetranslateUi();
 
 private:
-    void setConfiguration();
+    void SetConfiguration();
 
     std::pair<QString, QString> BCATDownloadEvents();
     void OnBCATImplChanged();

From 19c466dfb1f997eaa16fc9d9b832aaf3321adc40 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 1 Oct 2019 09:13:09 -0400
Subject: [PATCH 29/31] bcat: Add FSC accessors for BCAT data

Ports BCAT to use FSC interface
---
 src/core/hle/service/am/am.cpp                |  3 ++-
 src/core/hle/service/bcat/backend/backend.h   | 10 +++----
 src/core/hle/service/bcat/backend/boxcat.cpp  | 11 ++++----
 src/core/hle/service/bcat/bcat.cpp            |  4 +--
 src/core/hle/service/bcat/bcat.h              |  3 ++-
 src/core/hle/service/bcat/module.cpp          | 26 +++++++++++--------
 src/core/hle/service/bcat/module.h            | 19 +++++++++++---
 .../hle/service/filesystem/filesystem.cpp     |  2 +-
 src/core/hle/service/filesystem/filesystem.h  |  2 ++
 src/core/hle/service/service.cpp              |  2 +-
 10 files changed, 51 insertions(+), 31 deletions(-)

diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 79f9a393e..34409e0c3 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1140,7 +1140,8 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
     LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
 
     if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
-        const auto backend = BCAT::CreateBackendFromSettings(&FileSystem::GetBCATDirectory);
+        const auto backend = BCAT::CreateBackendFromSettings(
+            [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
         const auto build_id_full = Core::System::GetInstance().GetCurrentProcessBuildID();
         u64 build_id{};
         std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 50973a13a..3f5d8b5dd 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -57,11 +57,6 @@ static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
 class ProgressServiceBackend {
     friend class IBcatService;
 
-    ProgressServiceBackend(std::string event_name);
-
-    Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
-    DeliveryCacheProgressImpl& GetImpl();
-
 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)
@@ -90,6 +85,11 @@ public:
     void FinishDownload(ResultCode result);
 
 private:
+    explicit ProgressServiceBackend(std::string event_name);
+
+    Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
+    DeliveryCacheProgressImpl& GetImpl();
+
     void SignalUpdate() const;
 
     DeliveryCacheProgressImpl impl;
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 5bc2e22d7..2c3309268 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -364,17 +364,18 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
 
 bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
     is_syncing.exchange(true);
-    std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
-        .detach();
+    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();
+    std::thread([this, title, name, &progress] {
+        SynchronizeInternal(dir_getter, title, progress, name);
+    }).detach();
     return true;
 }
 
diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp
index 391f599ee..c2f946424 100644
--- a/src/core/hle/service/bcat/bcat.cpp
+++ b/src/core/hle/service/bcat/bcat.cpp
@@ -6,8 +6,8 @@
 
 namespace Service::BCAT {
 
-BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
-    : Module::Interface(std::move(module), name) {
+BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name)
+    : Module::Interface(std::move(module), fsc, name) {
     // clang-format off
     static const FunctionInfo functions[] = {
         {0, &BCAT::CreateBcatService, "CreateBcatService"},
diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h
index 802bd689a..813073658 100644
--- a/src/core/hle/service/bcat/bcat.h
+++ b/src/core/hle/service/bcat/bcat.h
@@ -10,7 +10,8 @@ namespace Service::BCAT {
 
 class BCAT final : public Module::Interface {
 public:
-    explicit BCAT(std::shared_ptr<Module> module, const char* name);
+    explicit BCAT(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 1b9a75a1c..b3fed56c7 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -539,7 +539,7 @@ void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestCont
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.PushIpcInterface<IDeliveryCacheStorageService>(
-        Service::FileSystem::GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
+        fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
 }
 
 void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
@@ -551,8 +551,7 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
-    rb.PushIpcInterface<IDeliveryCacheStorageService>(
-        Service::FileSystem::GetBCATDirectory(title_id));
+    rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
 }
 
 std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
@@ -566,18 +565,23 @@ std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
     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)),
-      backend(CreateBackendFromSettings(&Service::FileSystem::GetBCATDirectory)) {}
+Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
+                             const char* name)
+    : ServiceFramework(name), module(std::move(module)), fsc(fsc),
+      backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {}
 
 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>(module, system.GetFileSystemController(), "bcat:a")
+        ->InstallAsService(system.ServiceManager());
+    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m")
+        ->InstallAsService(system.ServiceManager());
+    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u")
+        ->InstallAsService(system.ServiceManager());
+    std::make_shared<BCAT>(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 fc52574c2..27469926a 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -6,7 +6,13 @@
 
 #include "core/hle/service/service.h"
 
-namespace Service::BCAT {
+namespace Service {
+
+namespace FileSystem {
+class FileSystemController;
+} // namespace FileSystem
+
+namespace BCAT {
 
 class Backend;
 
@@ -14,7 +20,8 @@ class Module final {
 public:
     class Interface : public ServiceFramework<Interface> {
     public:
-        explicit Interface(std::shared_ptr<Module> module, const char* name);
+        explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
+                           const char* name);
         ~Interface() override;
 
         void CreateBcatService(Kernel::HLERequestContext& ctx);
@@ -22,12 +29,16 @@ public:
         void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
 
     protected:
+        FileSystem::FileSystemController& fsc;
+
         std::shared_ptr<Module> module;
         std::unique_ptr<Backend> backend;
     };
 };
 
 /// Registers all BCAT services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(Core::System& system);
 
-} // namespace Service::BCAT
+} // namespace BCAT
+
+} // namespace Service
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 9cb107668..7fa4e820b 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -674,7 +674,7 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
     return bis_factory->GetModificationDumpRoot(title_id);
 }
 
-FileSys::VirtualDir GetBCATDirectory(u64 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)
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 3e0c03ec0..e6b49d8a2 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -110,6 +110,8 @@ public:
     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);
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 831a427de..f2c6fe9dc 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
     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);

From 5d86c52a3a5ec1037cc30e1a6d8abf914e3055a4 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 1 Oct 2019 09:13:31 -0400
Subject: [PATCH 30/31] boxcat: Use updated game-asset API URL and tags

---
 src/core/hle/service/bcat/backend/boxcat.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 2c3309268..36eb2c094 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -39,10 +39,10 @@ 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[] = "/boxcat/titles/{:016X}/data";
-constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/boxcat/titles/{:016X}/launchparam";
+constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
+constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
 
-constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events";
+constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
 
 constexpr char BOXCAT_API_VERSION[] = "1";
 constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
@@ -203,9 +203,9 @@ private:
         }
 
         httplib::Headers headers{
-            {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)},
+            {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
             {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
-            {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)},
+            {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
         };
 
         if (FileUtil::Exists(path)) {
@@ -444,7 +444,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
                               static_cast<int>(TIMEOUT_SECONDS)};
 
     httplib::Headers headers{
-        {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)},
+        {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
         {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
     };
 

From e55d086cc93ea33829e77a2e92be52bcf900767b Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Wed, 2 Oct 2019 08:35:39 -0400
Subject: [PATCH 31/31] qt: Add service dialog

---
 src/core/hle/service/bcat/backend/boxcat.cpp | 11 +++++------
 src/yuzu/CMakeLists.txt                      |  1 +
 src/yuzu/configuration/configure_dialog.cpp  |  4 +++-
 src/yuzu/configuration/configure_service.cpp | 19 ++++++++++---------
 src/yuzu/configuration/configure_service.h   |  2 +-
 5 files changed, 20 insertions(+), 17 deletions(-)

diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 36eb2c094..e6ee0810b 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -364,18 +364,17 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
 
 bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
     is_syncing.exchange(true);
-    std::thread([this, title, &progress] {
-        SynchronizeInternal(dir_getter, title, progress);
-    }).detach();
+    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();
+    std::thread(
+        [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
+        .detach();
     return true;
 }
 
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index fffb20220..ff1c1d985 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -68,6 +68,7 @@ add_executable(yuzu
     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
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 520b7e193..25b2e1b05 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -75,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}}},
     };
@@ -109,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
index 86160b479..81c9e933f 100644
--- a/src/yuzu/configuration/configure_service.cpp
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -20,7 +20,7 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
     if (status.events.size() == 1) {
         out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front()));
     } else {
-        for (const auto event : status.events) {
+        for (const auto& event : status.events) {
             out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event));
         }
     }
@@ -34,7 +34,7 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
 } // Anonymous namespace
 
 ConfigureService::ConfigureService(QWidget* parent)
-    : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()), watcher(this) {
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
     ui->setupUi(this);
 
     ui->bcat_source->addItem(QStringLiteral("None"));
@@ -62,7 +62,8 @@ void ConfigureService::RetranslateUi() {
 }
 
 void ConfigureService::SetConfiguration() {
-    int index = ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
+    const int index =
+        ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
     ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
 }
 
@@ -73,14 +74,14 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
 
     switch (res) {
     case Service::BCAT::Boxcat::StatusResult::Offline:
-        return {QStringLiteral(""),
+        return {QString{},
                 tr("The boxcat service is offline or you are not connected to the internet.")};
     case Service::BCAT::Boxcat::StatusResult::ParseError:
-        return {QStringLiteral(""),
+        return {QString{},
                 tr("There was an error while processing the boxcat event data. Contact the yuzu "
                    "developers.")};
     case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
-        return {QStringLiteral(""),
+        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.")};
     }
@@ -98,11 +99,11 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
 
     for (const auto& [key, value] : map) {
         out += QStringLiteral("%1<b>%2</b><br>%3")
-                   .arg(out.isEmpty() ? QStringLiteral("") : QStringLiteral("<br>"))
+                   .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
                    .arg(QString::fromStdString(key))
                    .arg(FormatEventStatusString(value));
     }
-    return {QStringLiteral("Current Boxcat Events"), out};
+    return {QStringLiteral("Current Boxcat Events"), std::move(out)};
 }
 
 void ConfigureService::OnBCATImplChanged() {
@@ -110,7 +111,7 @@ void ConfigureService::OnBCATImplChanged() {
     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(QStringLiteral(""));
+    ui->bcat_empty_header->setText(QString{});
     ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
 
     if (!boxcat)
diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_service.h
index efc8e21a8..f5c1b703a 100644
--- a/src/yuzu/configuration/configure_service.h
+++ b/src/yuzu/configuration/configure_service.h
@@ -30,5 +30,5 @@ private:
     void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
 
     std::unique_ptr<Ui::ConfigureService> ui;
-    QFutureWatcher<std::pair<QString, QString>> watcher;
+    QFutureWatcher<std::pair<QString, QString>> watcher{this};
 };