From 84b605901299c9a8ea01d461109580710cabfb48 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 3a49c48744..f3051cca0c 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 e6fa11a036..d797d9fc95 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 0000000000..bebbb54c8e --- /dev/null +++ b/externals/libzip @@ -0,0 +1 @@ +Subproject commit bebbb54c8e691f019415fcb852ef4d53ebbc5000 diff --git a/externals/zlib b/externals/zlib new file mode 160000 index 0000000000..094ed57db3 --- /dev/null +++ b/externals/zlib @@ -0,0 +1 @@ +Subproject commit 094ed57db392170130bc710293568de7b576306d From c00ed8f4ffc4e6fca6337aecaa1acd390c71a584 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 0000000000..64f19a0eae --- /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 +#include +#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 out = std::make_shared(); + + 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 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(buf, parts.back()); + + std::shared_ptr 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( + std::vector{}, std::vector{}, parts[j]); + dtrv->AddDirectory(temp); + dtrv = temp; + } else { + dtrv = std::dynamic_pointer_cast(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 0000000000..f68af576a6 --- /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 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 8f758d6d9d..0af44f3409 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(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 bdfe728c9f..8f0451c982 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 14cd0e3229..9cb1076684 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 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 d2e35362fe..720fe766f2 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 764c3418c6..226be88b10 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 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 92ba42fb9c..75a7ffb973 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -339,6 +339,7 @@ struct System::Impl { std::unique_ptr cheat_engine; std::unique_ptr memory_freezer; + std::array 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 id) { + impl->build_id = id; +} + +const std::array& 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 ff10ebe12b..f49b7fbf9f 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -330,6 +330,10 @@ public: bool GetExitLock() const; + void SetCurrentProcessBuildID(std::array id); + + const std::array& GetCurrentProcessBuildID() const; + private: System(); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e75c700ad1..f629892aea 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -150,6 +150,7 @@ std::optional 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 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 24d1813a74..756a2af57e 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 { public: explicit IScanRequest() : ServiceFramework("IScanRequest") { @@ -81,7 +88,7 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(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(0); + rb.Push(1); } void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(0); + rb.Push(1); } Core::System& system; }; From 647992e666617d287a06b4ffbd1db9ab6cbd524d Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 7de3fd1e5b..d1fc940605 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 47bddfb30d..9c98a92873 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 92d9fb1610..ac7a773650 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 6b523ecdde..ba6888004e 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 d82438502d..1a812cb878 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 a6171c3edc..8d18a4a5a9 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 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 0000000000..aefa2208db --- /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 0000000000..2e9511f3f6 --- /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 +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" + +namespace Service::BCAT { + +using CompletionCallback = std::function; +using DirectoryGetter = std::function; +using Passphrase = std::array; + +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 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 0000000000..539140f303 --- /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 +#include +#include +#include +#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 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(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(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(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 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(ResponseStatus::NoUpdate)) + return DownloadResult::Success; + if (response->status == static_cast(ResponseStatus::BadClientVersion)) + return DownloadResult::BadClientVersion; + if (response->status == static_cast(ResponseStatus::NoMatchTitleId)) + return DownloadResult::NoMatchTitleId; + if (response->status == static_cast(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; + static Digest DigestFile(std::vector bytes) { + Digest out{}; + mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0); + return out; + } + + std::unique_ptr 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 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 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(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 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& global, + std::map& games) { + httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast(PORT), + static_cast(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(ResponseStatus::BadClientVersion)) + return StatusResult::BadClientVersion; + + try { + nlohmann::json json = nlohmann::json::parse(response->body); + + if (!json["online"].get()) + return StatusResult::Offline; + + if (json["global"].is_null()) + global = std::nullopt; + else + global = json["global"].get(); + + 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(); + } else { + detail.header = std::nullopt; + } + + if (object["footer"].is_string()) { + detail.footer = object["footer"].get(); + } 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()); + } + } + + 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 0000000000..f4e60f264a --- /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 +#include +#include +#include "core/hle/service/bcat/backend/backend.h" + +namespace Service::BCAT { + +struct EventStatus { + std::optional header; + std::optional footer; + std::vector 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 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& global, + std::map& games); + +private: + std::atomic_bool is_syncing{false}; + + class Client; + std::unique_ptr client; +}; + +} // namespace Service::BCAT From 68658a8385b74454c8523efe95ceb81b34bb8812 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 a6b56c9c6c..3416854db3 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 b7bd738fcb..32d3d5cfc3 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(); +namespace { +std::unique_ptr CreateBackendFromSettings(DirectoryGetter getter) { + const auto backend = Settings::values.bcat_backend; + +#ifdef YUZU_ENABLE_BOXCAT + if (backend == "boxcat") + return std::make_unique(std::move(getter)); +#endif + + return std::make_unique(std::move(getter)); } +} // Anonymous namespace Module::Interface::Interface(std::shared_ptr 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 f0d63cab02..4af363bfd0 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 { @@ -19,6 +21,7 @@ public: protected: std::shared_ptr module; + std::unique_ptr backend; }; }; From 78d146f907cbab60026f972e1be7cc8eb83e05bb Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 179aa49499..391f599ee2 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, 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 32d3d5cfc3..fd742fde22 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 { 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(); + rb.PushIpcInterface(*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( + Service::FileSystem::GetBCATDirectory(Core::CurrentProcess()->GetTitleID())); +} + +void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface( + Service::FileSystem::GetBCATDirectory(title_id)); +} + namespace { std::unique_ptr 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 4af363bfd0..fc52574c24 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; From 862131ead9cf8e10e4ff220d01cb1be16533d208 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 fd742fde22..2d83413590 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(*backend); +class IDeliveryCacheStorageService final : public ServiceFramework { +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(root); + } + + void CreateDirectoryService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(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(size); + } + + FileSys::VirtualDir root; + std::vector 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 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 2d83413590..6645599f28 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(*backend); +class IDeliveryCacheDirectoryService final + : public ServiceFramework { +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(); + 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 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(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(files.size()); + } + + FileSys::VirtualDir root; + FileSys::VirtualDir current_dir; +}; + class IDeliveryCacheStorageService final : public ServiceFramework { public: IDeliveryCacheStorageService(FileSys::VirtualDir root_) From f352ad5c93aa0266cba8b4c15ee3f0c5f0b0aed2 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 6645599f28..25f68ed636 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(*backend); + +class IDeliveryCacheFileService final : public ServiceFramework { +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(); + const auto file_name_raw = rp.PopRaw(); + + 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()}; + + 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(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(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 { public: From cb7c96b96a0ad781d5bb4387072d5d0becd74dd7 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 25f68ed636..1459fab113 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 +#include +#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; + +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 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(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 name) { + return VerifyNameValidInternal(ctx, name, '-'); +} + +bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array name) { + return VerifyNameValidInternal(ctx, name, '.'); +} + +} // Anonymous namespace + +using DirectoryName = std::array; +using FileName = std::array; + +struct DeliveryCacheDirectoryEntry { + FileName name; + u64 size; + BCATDigest digest; +}; + +class IDeliveryCacheProgressService final : public ServiceFramework { +public: + IDeliveryCacheProgressService(Kernel::SharedPtr 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 event; + const DeliveryCacheProgressImpl& impl; +}; + class IBcatService final : public ServiceFramework { public: IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) { From 86773a7f081a8a6c71643ecdc6573b65dbf0ccd3 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 1459fab113..605aa6e007 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(SyncType::Normal)).event = + Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, + "BCAT::IDeliveryCacheProgressEvent"); + progress.at(static_cast(SyncType::Directory)).event = + Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::OneShot, + "BCAT::IDeliveryCacheProgressEvent::DirectoryName"); } + +private: + enum class SyncType { + Normal, + Directory, + Count, + }; + + std::function CreateCallback(SyncType type) { + return [this, type](bool success) { + auto& pair{progress.at(static_cast(type))}; + pair.impl.status = DeliveryCacheProgressImpl::Status::Complete; + pair.event.writable->Signal(); + }; + } + + std::shared_ptr CreateProgressService(SyncType type) { + const auto& pair{progress.at(static_cast(type))}; + return std::make_shared(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(); + 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(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(*backend); +} class IDeliveryCacheFileService final : public ServiceFramework { public: From 1bde5a3c6a205de445b2086f2fda2a830d70b8f6 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 605aa6e007..cbda8e0d38 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(); + + 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 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 cbda8e0d38..9244c265a3 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(); + + 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 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 dc6fa07fc2..fffb202204 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 49fadd0ef7..372427ae20 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -98,6 +98,11 @@ Web + + + Services + + @@ -178,6 +183,12 @@
configuration/configure_hotkeys.h
1 + + ConfigureService + QWidget +
configuration/configure_service.h
+ 1 +
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 7c875ae870..520b7e1938 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 0000000000..fca785d0ea --- /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 +#include +#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("%1
").arg(QString::fromStdString(*status.header)); + } + + if (status.events.size() == 1) { + out += QStringLiteral("%1
").arg(QString::fromStdString(status.events.front())); + } else { + for (const auto event : status.events) { + out += QStringLiteral("- %1
").arg(QString::fromStdString(event)); + } + } + + if (status.footer.has_value()) { + out += QStringLiteral("%1
").arg(QString::fromStdString(*status.footer)); + } + + return out; +} +} // Anonymous namespace + +ConfigureService::ConfigureService(QWidget* parent) + : QWidget(parent), ui(std::make_unique()), 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::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 ConfigureService::BCATDownloadEvents() { + std::optional global; + std::map 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%2
%3") + .arg(out.isEmpty() ? "" : "
") + .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>::finished, this, + [this] { OnUpdateBCATEmptyLabel(watcher.result()); }); +#endif +} + +void ConfigureService::OnUpdateBCATEmptyLabel(std::pair 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 0000000000..ee50d5a799 --- /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 +#include +#include + +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 BCATDownloadEvents(); + void OnBCATImplChanged(); + void OnUpdateBCATEmptyLabel(std::pair string); + + std::unique_ptr ui; + QFutureWatcher> watcher; +}; diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui new file mode 100644 index 0000000000..f5b683d25e --- /dev/null +++ b/src/yuzu/configuration/configure_service.ui @@ -0,0 +1,121 @@ + + + ConfigureService + + + + 0 + 0 + 433 + 561 + + + + Form + + + + + + + + BCAT + + + + + + + 260 + 16777215 + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + + + true + + + + + + + + 16777215 + 16777215 + + + + BCAT Backend + + + + + + + true + + + + 260 + 16777215 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + + + + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + From d8bcb1e9739e5c672d62df01a013812e082eefb5 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 bfa104034c..9b3b0d6d50 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 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 f5b683d25e..9668dd5577 100644 --- a/src/yuzu/configuration/configure_service.ui +++ b/src/yuzu/configuration/configure_service.ui @@ -78,6 +78,9 @@ <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + + true + From fe8c7e66e291b1fb3bef206e0d5809ad80441e9b Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 797c9a06fc..79f9a393ef 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(); - LaunchParameters params{}; + LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast(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(*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 buffer(sizeof(LaunchParameters)); - std::memcpy(buffer.data(), ¶ms, 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(buffer); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + + std::vector buffer(sizeof(LaunchParameterAccountPreselectedUser)); + std::memcpy(buffer.data(), ¶ms, buffer.size()); + + rb.PushIpcInterface(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 a3baeb6730..9169eb2bda 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 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 2e9511f3f6..e412819e15 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 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 9244c265a3..a8d545992d 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 CreateBackendFromSettings(DirectoryGetter getter) { const auto backend = Settings::values.bcat_backend; @@ -600,7 +599,6 @@ std::unique_ptr CreateBackendFromSettings(DirectoryGetter getter) { return std::make_unique(std::move(getter)); } -} // Anonymous namespace Module::Interface::Interface(std::shared_ptr module, const char* name) : ServiceFramework(name), module(std::move(module)), From b8ce87103d1aa382a9b96f3379a599f869b02a68 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 aefa2208db..9a67da2efd 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> 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 e412819e15..5b41188148 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 +#include #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> 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> GetLaunchParameter(TitleIDVersion title) override; }; std::unique_ptr CreateBackendFromSettings(DirectoryGetter getter); From e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 539140f303..f37f92bf48 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(BOXCAT_HOSTNAME, PORT, TIMEOUT_SECONDS); + client = std::make_unique(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 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(ResponseStatus::NoMatchBuildId)) return DownloadResult::NoMatchBuildId; - if (response->status >= 400) + if (response->status != static_cast(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; static Digest DigestFile(std::vector bytes) { Digest out{}; @@ -163,7 +183,7 @@ private: } std::unique_ptr 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> 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 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& global, std::map& games) { httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast(PORT), @@ -293,6 +351,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional& 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 f4e60f264a..1148a4ecaa 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> GetLaunchParameter(TitleIDVersion title) override; + enum class StatusResult { Success, Offline, From 92b70a3bf9d4657dccc5a5f2cffdd7789946ca14 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 64f19a0eae..e34474ae09 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 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{zip_open_from_source(src.get(), 0, &error), + zip_discard}; if (zip == nullptr) return nullptr; std::shared_ptr out = std::make_shared(); - 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 file{ + zip_fopen_index(zip.get(), i, 0), zip_fclose}; std::vector 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(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 f37f92bf48..31d2e045c2 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(BOXCAT_HOSTNAME, PORT, timeout_seconds); @@ -136,10 +134,13 @@ private: if (FileUtil::Exists(path)) { FileUtil::IOFile file{path, "rb"}; - std::vector 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 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 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> Boxcat::GetLaunchParameter(TitleIDVersion title) FileUtil::IOFile bin{path, "rb"}; const auto size = bin.GetSize(); std::vector 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 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 e34474ae09..8bdaa7e4ad 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 src{ - zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_free}; + std::unique_ptr src{ + zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close}; if (src == nullptr) return nullptr; - std::unique_ptr zip{zip_open_from_source(src.get(), 0, &error), - zip_discard}; + std::unique_ptr 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 9a67da2efd..e389ad568e 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 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 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 5b41188148..50973a13ac 100644 --- a/src/core/hle/service/bcat/backend/backend.h +++ b/src/core/hle/service/bcat/backend/backend.h @@ -8,10 +8,14 @@ #include #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; +struct DeliveryCacheProgressImpl; + using DirectoryGetter = std::function; using Passphrase = std::array; @@ -20,33 +24,116 @@ struct TitleIDVersion { u64 build_id; }; +using DirectoryName = std::array; +using FileName = std::array; + +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 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> 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 31d2e045c2..3754594dfb 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(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 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 dir_name = {}) { - const auto failure = [&callback] { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - callback(false); - }; + ProgressServiceBackend& progress, + std::optional 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 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(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 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 1148a4ecaa..6011511899 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 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 a8d545992d..d5f9e9d3b3 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; -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 name) { +bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) { return VerifyNameValidInternal(ctx, name, '-'); } -bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array name) { +bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) { return VerifyNameValidInternal(ctx, name, '.'); } } // Anonymous namespace -using DirectoryName = std::array; -using FileName = std::array; - 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(SyncType::Normal)).event = - Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky, - "BCAT::IDeliveryCacheProgressEvent"); - progress.at(static_cast(SyncType::Directory)).event = - Kernel::WritableEvent::CreateEventPair( - kernel, Kernel::ResetType::OneShot, - "BCAT::IDeliveryCacheProgressEvent::DirectoryName"); } private: @@ -180,24 +154,17 @@ private: Count, }; - std::function CreateCallback(SyncType type) { - return [this, type](bool success) { - auto& pair{progress.at(static_cast(type))}; - pair.impl.status = DeliveryCacheProgressImpl::Status::Complete; - pair.event.writable->Signal(); - }; - } - std::shared_ptr CreateProgressService(SyncType type) { - const auto& pair{progress.at(static_cast(type))}; - return std::make_shared(pair.event.readable, pair.impl); + auto& backend{progress.at(static_cast(type))}; + return std::make_shared(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(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(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(SyncType::Count)> progress{ + ProgressServiceBackend{"Normal"}, + ProgressServiceBackend{"Directory"}, }; - - std::array(SyncType::Count)> progress{}; }; void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { From bcf1eafb8bd1a810fd33a7e7e06a86173b4bfb9f Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 bebbb54c8e..bd7a8103e9 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 e389ad568e..9b677debee 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 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(dir_name.size(), 0x31ull)); + std::memcpy(impl.current_file.data(), file_name.data(), + std::min(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(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> 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 3754594dfb..5bc2e22d77 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 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> Boxcat::GetLaunchParameter(TitleIDVersion title) { diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index d5f9e9d3b3..1b9a75a1cc 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(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(write_size, files.size()); std::vector 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(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 ac7a773650..4cb27ddb29 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 fca785d0ea..86160b4791 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::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 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 ConfigureService::BCATDownloadEvents() { } QString out; + + if (global.has_value()) { + out += QStringLiteral("%1
").arg(QString::fromStdString(*global)); + } + for (const auto& [key, value] : map) { out += QStringLiteral("%1%2
%3") - .arg(out.isEmpty() ? "" : "
") + .arg(out.isEmpty() ? QStringLiteral("") : QStringLiteral("
")) .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 ee50d5a799..efc8e21a83 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 BCATDownloadEvents(); void OnBCATImplChanged(); From 19c466dfb1f997eaa16fc9d9b832aaf3321adc40 Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 79f9a393ef..34409e0c37 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(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 50973a13ac..3f5d8b5ddd 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 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 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 5bc2e22d77..2c33092687 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 391f599ee2..c2f9464240 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, const char* name) - : Module::Interface(std::move(module), name) { +BCAT::BCAT(std::shared_ptr 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 802bd689af..8130736588 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, const char* name); + explicit BCAT(std::shared_ptr 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 1b9a75a1cc..b3fed56c7b 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( - 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( - Service::FileSystem::GetBCATDirectory(title_id)); + rb.PushIpcInterface(fsc.GetBCATDirectory(title_id)); } std::unique_ptr CreateBackendFromSettings(DirectoryGetter getter) { @@ -566,18 +565,23 @@ std::unique_ptr CreateBackendFromSettings(DirectoryGetter getter) { return std::make_unique(std::move(getter)); } -Module::Interface::Interface(std::shared_ptr module, const char* name) - : ServiceFramework(name), module(std::move(module)), - backend(CreateBackendFromSettings(&Service::FileSystem::GetBCATDirectory)) {} +Module::Interface::Interface(std::shared_ptr 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(); - std::make_shared(module, "bcat:a")->InstallAsService(service_manager); - std::make_shared(module, "bcat:m")->InstallAsService(service_manager); - std::make_shared(module, "bcat:u")->InstallAsService(service_manager); - std::make_shared(module, "bcat:s")->InstallAsService(service_manager); + std::make_shared(module, system.GetFileSystemController(), "bcat:a") + ->InstallAsService(system.ServiceManager()); + std::make_shared(module, system.GetFileSystemController(), "bcat:m") + ->InstallAsService(system.ServiceManager()); + std::make_shared(module, system.GetFileSystemController(), "bcat:u") + ->InstallAsService(system.ServiceManager()); + std::make_shared(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 fc52574c24..27469926aa 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 { public: - explicit Interface(std::shared_ptr module, const char* name); + explicit Interface(std::shared_ptr 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; std::unique_ptr 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 9cb1076684..7fa4e820be 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 3e0c03ec0f..e6b49d8a20 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 831a427de6..f2c6fe9dc9 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, 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 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 2c33092687..36eb2c0945 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& global, static_cast(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 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 36eb2c0945..e6ee0810b2 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 fffb202204..ff1c1d9856 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 520b7e1938..25b2e1b05b 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -75,7 +75,8 @@ Q_DECLARE_METATYPE(QList); void ConfigureDialog::PopulateSelectionList() { const std::array>, 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 86160b4791..81c9e933f3 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
").arg(QString::fromStdString(status.events.front())); } else { - for (const auto event : status.events) { + for (const auto& event : status.events) { out += QStringLiteral("- %1
").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()), watcher(this) { + : QWidget(parent), ui(std::make_unique()) { 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 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 ConfigureService::BCATDownloadEvents() { for (const auto& [key, value] : map) { out += QStringLiteral("%1%2
%3") - .arg(out.isEmpty() ? QStringLiteral("") : QStringLiteral("
")) + .arg(out.isEmpty() ? QString{} : QStringLiteral("
")) .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 efc8e21a83..f5c1b703a8 100644 --- a/src/yuzu/configuration/configure_service.h +++ b/src/yuzu/configuration/configure_service.h @@ -30,5 +30,5 @@ private: void OnUpdateBCATEmptyLabel(std::pair string); std::unique_ptr ui; - QFutureWatcher> watcher; + QFutureWatcher> watcher{this}; };