Merge pull request #2539 from DarkLordZach/bcat
bcat: Implement BCAT service and connect to yuzu Boxcat server
This commit is contained in:
		@@ -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,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    LOG_DEBUG(Service_AM, "called");
 | 
			
		||||
    IPC::RequestParser rp{ctx};
 | 
			
		||||
    const auto kind = rp.PopEnum<LaunchParameterKind>();
 | 
			
		||||
 | 
			
		||||
    LaunchParameters params{};
 | 
			
		||||
    LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
 | 
			
		||||
 | 
			
		||||
    params.magic = POP_LAUNCH_PARAMETER_MAGIC;
 | 
			
		||||
    params.is_account_selected = 1;
 | 
			
		||||
    if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
 | 
			
		||||
        const auto backend = BCAT::CreateBackendFromSettings(
 | 
			
		||||
            [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); });
 | 
			
		||||
        const auto build_id_full = Core::System::GetInstance().GetCurrentProcessBuildID();
 | 
			
		||||
        u64 build_id{};
 | 
			
		||||
        std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
 | 
			
		||||
 | 
			
		||||
    Account::ProfileManager profile_manager{};
 | 
			
		||||
    const auto uuid = profile_manager.GetUser(Settings::values.current_user);
 | 
			
		||||
    ASSERT(uuid);
 | 
			
		||||
    params.current_user = uuid->uuid;
 | 
			
		||||
        const auto data =
 | 
			
		||||
            backend->GetLaunchParameter({Core::CurrentProcess()->GetTitleID(), build_id});
 | 
			
		||||
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
        if (data.has_value()) {
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
            rb.Push(RESULT_SUCCESS);
 | 
			
		||||
            rb.PushIpcInterface<AM::IStorage>(*data);
 | 
			
		||||
            launch_popped_application_specific = true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
 | 
			
		||||
               !launch_popped_account_preselect) {
 | 
			
		||||
        LaunchParameterAccountPreselectedUser params{};
 | 
			
		||||
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
 | 
			
		||||
        params.is_account_selected = 1;
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> buffer(sizeof(LaunchParameters));
 | 
			
		||||
    std::memcpy(buffer.data(), ¶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<AM::IStorage>(buffer);
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
        std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
 | 
			
		||||
        std::memcpy(buffer.data(), ¶ms, buffer.size());
 | 
			
		||||
 | 
			
		||||
        rb.PushIpcInterface<AM::IStorage>(buffer);
 | 
			
		||||
        launch_popped_account_preselect = true;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
    rb.Push(ERR_NO_DATA_IN_CHANNEL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -190,6 +190,8 @@ public:
 | 
			
		||||
    explicit AppletManager(Core::System& system_);
 | 
			
		||||
    ~AppletManager();
 | 
			
		||||
 | 
			
		||||
    const AppletFrontendSet& GetAppletFrontendSet() const;
 | 
			
		||||
 | 
			
		||||
    void SetAppletFrontendSet(AppletFrontendSet set);
 | 
			
		||||
    void SetDefaultAppletFrontendSet();
 | 
			
		||||
    void SetDefaultAppletsIfMissing();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										136
									
								
								src/core/hle/service/bcat/backend/backend.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/core/hle/service/bcat/backend/backend.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
// Copyright 2019 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/hle/lock.h"
 | 
			
		||||
#include "core/hle/service/bcat/backend/backend.h"
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} {
 | 
			
		||||
    auto& kernel{Core::System::GetInstance().Kernel()};
 | 
			
		||||
    event = Kernel::WritableEvent::CreateEventPair(
 | 
			
		||||
        kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
 | 
			
		||||
    return event.readable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
 | 
			
		||||
    return impl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::SetNeedHLELock(bool need) {
 | 
			
		||||
    need_hle_lock = need;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::SetTotalSize(u64 size) {
 | 
			
		||||
    impl.total_bytes = size;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::StartConnecting() {
 | 
			
		||||
    impl.status = DeliveryCacheProgressImpl::Status::Connecting;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::StartProcessingDataList() {
 | 
			
		||||
    impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
 | 
			
		||||
                                                  std::string_view file_name, u64 file_size) {
 | 
			
		||||
    impl.status = DeliveryCacheProgressImpl::Status::Downloading;
 | 
			
		||||
    impl.current_downloaded_bytes = 0;
 | 
			
		||||
    impl.current_total_bytes = file_size;
 | 
			
		||||
    std::memcpy(impl.current_directory.data(), dir_name.data(),
 | 
			
		||||
                std::min<u64>(dir_name.size(), 0x31ull));
 | 
			
		||||
    std::memcpy(impl.current_file.data(), file_name.data(),
 | 
			
		||||
                std::min<u64>(file_name.size(), 0x31ull));
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
 | 
			
		||||
    impl.current_downloaded_bytes = downloaded;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::FinishDownloadingFile() {
 | 
			
		||||
    impl.total_downloaded_bytes += impl.current_total_bytes;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
 | 
			
		||||
    impl.status = DeliveryCacheProgressImpl::Status::Committing;
 | 
			
		||||
    impl.current_file.fill(0);
 | 
			
		||||
    impl.current_downloaded_bytes = 0;
 | 
			
		||||
    impl.current_total_bytes = 0;
 | 
			
		||||
    std::memcpy(impl.current_directory.data(), dir_name.data(),
 | 
			
		||||
                std::min<u64>(dir_name.size(), 0x31ull));
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::FinishDownload(ResultCode result) {
 | 
			
		||||
    impl.total_downloaded_bytes = impl.total_bytes;
 | 
			
		||||
    impl.status = DeliveryCacheProgressImpl::Status::Done;
 | 
			
		||||
    impl.result = result;
 | 
			
		||||
    SignalUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProgressServiceBackend::SignalUpdate() const {
 | 
			
		||||
    if (need_hle_lock) {
 | 
			
		||||
        std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
 | 
			
		||||
        event.writable->Signal();
 | 
			
		||||
    } else {
 | 
			
		||||
        event.writable->Signal();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
 | 
			
		||||
 | 
			
		||||
Backend::~Backend() = default;
 | 
			
		||||
 | 
			
		||||
NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {}
 | 
			
		||||
 | 
			
		||||
NullBackend::~NullBackend() = default;
 | 
			
		||||
 | 
			
		||||
bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
 | 
			
		||||
              title.build_id);
 | 
			
		||||
 | 
			
		||||
    progress.FinishDownload(RESULT_SUCCESS);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
 | 
			
		||||
                                       ProgressServiceBackend& progress) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
 | 
			
		||||
              title.build_id, name);
 | 
			
		||||
 | 
			
		||||
    progress.FinishDownload(RESULT_SUCCESS);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NullBackend::Clear(u64 title_id) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}");
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id,
 | 
			
		||||
              Common::HexToString(passphrase));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
 | 
			
		||||
              title.build_id);
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Service::BCAT
 | 
			
		||||
							
								
								
									
										147
									
								
								src/core/hle/service/bcat/backend/backend.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/core/hle/service/bcat/backend/backend.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
// Copyright 2019 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/file_sys/vfs_types.h"
 | 
			
		||||
#include "core/hle/kernel/readable_event.h"
 | 
			
		||||
#include "core/hle/kernel/writable_event.h"
 | 
			
		||||
#include "core/hle/result.h"
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
struct DeliveryCacheProgressImpl;
 | 
			
		||||
 | 
			
		||||
using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
 | 
			
		||||
using Passphrase = std::array<u8, 0x20>;
 | 
			
		||||
 | 
			
		||||
struct TitleIDVersion {
 | 
			
		||||
    u64 title_id;
 | 
			
		||||
    u64 build_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using DirectoryName = std::array<char, 0x20>;
 | 
			
		||||
using FileName = std::array<char, 0x20>;
 | 
			
		||||
 | 
			
		||||
struct DeliveryCacheProgressImpl {
 | 
			
		||||
    enum class Status : s32 {
 | 
			
		||||
        None = 0x0,
 | 
			
		||||
        Queued = 0x1,
 | 
			
		||||
        Connecting = 0x2,
 | 
			
		||||
        ProcessingDataList = 0x3,
 | 
			
		||||
        Downloading = 0x4,
 | 
			
		||||
        Committing = 0x5,
 | 
			
		||||
        Done = 0x9,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Status status;
 | 
			
		||||
    ResultCode result = RESULT_SUCCESS;
 | 
			
		||||
    DirectoryName current_directory;
 | 
			
		||||
    FileName current_file;
 | 
			
		||||
    s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
 | 
			
		||||
    s64 current_total_bytes;      ///< Bytes total on current file.
 | 
			
		||||
    s64 total_downloaded_bytes;   ///< Bytes downloaded on overall download.
 | 
			
		||||
    s64 total_bytes;              ///< Bytes total on overall download.
 | 
			
		||||
    INSERT_PADDING_BYTES(
 | 
			
		||||
        0x198); ///< Appears to be unused in official code, possibly reserved for future use.
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
 | 
			
		||||
              "DeliveryCacheProgressImpl has incorrect size.");
 | 
			
		||||
 | 
			
		||||
// A class to manage the signalling to the game about BCAT download progress.
 | 
			
		||||
// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
 | 
			
		||||
class ProgressServiceBackend {
 | 
			
		||||
    friend class IBcatService;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    // Clients should call this with true if any of the functions are going to be called from a
 | 
			
		||||
    // non-HLE thread and this class need to lock the hle mutex. (default is false)
 | 
			
		||||
    void SetNeedHLELock(bool need);
 | 
			
		||||
 | 
			
		||||
    // Sets the number of bytes total in the entire download.
 | 
			
		||||
    void SetTotalSize(u64 size);
 | 
			
		||||
 | 
			
		||||
    // Notifies the application that the backend has started connecting to the server.
 | 
			
		||||
    void StartConnecting();
 | 
			
		||||
    // Notifies the application that the backend has begun accumulating and processing metadata.
 | 
			
		||||
    void StartProcessingDataList();
 | 
			
		||||
 | 
			
		||||
    // Notifies the application that a file is starting to be downloaded.
 | 
			
		||||
    void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
 | 
			
		||||
    // Updates the progress of the current file to the size passed.
 | 
			
		||||
    void UpdateFileProgress(u64 downloaded);
 | 
			
		||||
    // Notifies the application that the current file has completed download.
 | 
			
		||||
    void FinishDownloadingFile();
 | 
			
		||||
 | 
			
		||||
    // Notifies the application that all files in this directory have completed and are being
 | 
			
		||||
    // finalized.
 | 
			
		||||
    void CommitDirectory(std::string_view dir_name);
 | 
			
		||||
 | 
			
		||||
    // Notifies the application that the operation completed with result code result.
 | 
			
		||||
    void FinishDownload(ResultCode result);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    explicit ProgressServiceBackend(std::string event_name);
 | 
			
		||||
 | 
			
		||||
    Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
 | 
			
		||||
    DeliveryCacheProgressImpl& GetImpl();
 | 
			
		||||
 | 
			
		||||
    void SignalUpdate() const;
 | 
			
		||||
 | 
			
		||||
    DeliveryCacheProgressImpl impl;
 | 
			
		||||
    Kernel::EventPair event;
 | 
			
		||||
    bool need_hle_lock = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A class representing an abstract backend for BCAT functionality.
 | 
			
		||||
class Backend {
 | 
			
		||||
public:
 | 
			
		||||
    explicit Backend(DirectoryGetter getter);
 | 
			
		||||
    virtual ~Backend();
 | 
			
		||||
 | 
			
		||||
    // Called when the backend is needed to synchronize the data for the game with title ID and
 | 
			
		||||
    // version in title. A ProgressServiceBackend object is provided to alert the application of
 | 
			
		||||
    // status.
 | 
			
		||||
    virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
 | 
			
		||||
    // Very similar to Synchronize, but only for the directory provided. Backends should not alter
 | 
			
		||||
    // the data for any other directories.
 | 
			
		||||
    virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
 | 
			
		||||
                                      ProgressServiceBackend& progress) = 0;
 | 
			
		||||
 | 
			
		||||
    // Removes all cached data associated with title id provided.
 | 
			
		||||
    virtual bool Clear(u64 title_id) = 0;
 | 
			
		||||
 | 
			
		||||
    // Sets the BCAT Passphrase to be used with the associated title ID.
 | 
			
		||||
    virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
 | 
			
		||||
 | 
			
		||||
    // Gets the launch parameter used by AM associated with the title ID and version provided.
 | 
			
		||||
    virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    DirectoryGetter dir_getter;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// A backend of BCAT that provides no operation.
 | 
			
		||||
class NullBackend : public Backend {
 | 
			
		||||
public:
 | 
			
		||||
    explicit NullBackend(const DirectoryGetter& getter);
 | 
			
		||||
    ~NullBackend() override;
 | 
			
		||||
 | 
			
		||||
    bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
 | 
			
		||||
    bool SynchronizeDirectory(TitleIDVersion title, std::string name,
 | 
			
		||||
                              ProgressServiceBackend& progress) override;
 | 
			
		||||
 | 
			
		||||
    bool Clear(u64 title_id) override;
 | 
			
		||||
 | 
			
		||||
    void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
 | 
			
		||||
 | 
			
		||||
    std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter);
 | 
			
		||||
 | 
			
		||||
} // namespace Service::BCAT
 | 
			
		||||
							
								
								
									
										503
									
								
								src/core/hle/service/bcat/backend/boxcat.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										503
									
								
								src/core/hle/service/bcat/backend/boxcat.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,503 @@
 | 
			
		||||
// Copyright 2019 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <fmt/ostream.h>
 | 
			
		||||
#include <httplib.h>
 | 
			
		||||
#include <json.hpp>
 | 
			
		||||
#include <mbedtls/sha256.h>
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/backend.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/file_sys/vfs_libzip.h"
 | 
			
		||||
#include "core/file_sys/vfs_vector.h"
 | 
			
		||||
#include "core/frontend/applets/error.h"
 | 
			
		||||
#include "core/hle/service/am/applets/applets.h"
 | 
			
		||||
#include "core/hle/service/bcat/backend/boxcat.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
// Prevents conflicts with windows macro called CreateFile
 | 
			
		||||
FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
 | 
			
		||||
    return dir->CreateFile(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prevents conflicts with windows macro called DeleteFile
 | 
			
		||||
bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
 | 
			
		||||
    return dir->DeleteFile(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
 | 
			
		||||
 | 
			
		||||
constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
 | 
			
		||||
 | 
			
		||||
// Formatted using fmt with arg[0] = hex title id
 | 
			
		||||
constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat";
 | 
			
		||||
constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam";
 | 
			
		||||
 | 
			
		||||
constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events";
 | 
			
		||||
 | 
			
		||||
constexpr char BOXCAT_API_VERSION[] = "1";
 | 
			
		||||
constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
 | 
			
		||||
 | 
			
		||||
// HTTP status codes for Boxcat
 | 
			
		||||
enum class ResponseStatus {
 | 
			
		||||
    Ok = 200,               ///< Operation completed successfully.
 | 
			
		||||
    BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
 | 
			
		||||
    NoUpdate = 304,         ///< The digest provided would match the new data, no need to update.
 | 
			
		||||
    NoMatchTitleId = 404,   ///< The title ID provided doesn't have a boxcat implementation.
 | 
			
		||||
    NoMatchBuildId = 406,   ///< The build ID provided is blacklisted (potentially because of format
 | 
			
		||||
                            ///< issues or whatnot) and has no data.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class DownloadResult {
 | 
			
		||||
    Success = 0,
 | 
			
		||||
    NoResponse,
 | 
			
		||||
    GeneralWebError,
 | 
			
		||||
    NoMatchTitleId,
 | 
			
		||||
    NoMatchBuildId,
 | 
			
		||||
    InvalidContentType,
 | 
			
		||||
    GeneralFSError,
 | 
			
		||||
    BadClientVersion,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{
 | 
			
		||||
    "Success",
 | 
			
		||||
    "There was no response from the server.",
 | 
			
		||||
    "There was a general web error code returned from the server.",
 | 
			
		||||
    "The title ID of the current game doesn't have a boxcat implementation. If you believe an "
 | 
			
		||||
    "implementation should be added, contact yuzu support.",
 | 
			
		||||
    "The build ID of the current version of the game is marked as incompatible with the current "
 | 
			
		||||
    "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.",
 | 
			
		||||
    "The content type of the web response was invalid.",
 | 
			
		||||
    "There was a general filesystem error while saving the zip file.",
 | 
			
		||||
    "The server is either too new or too old to serve the request. Try using the latest version of "
 | 
			
		||||
    "an official release of yuzu.",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::ostream& operator<<(std::ostream& os, DownloadResult result) {
 | 
			
		||||
    return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr u32 PORT = 443;
 | 
			
		||||
constexpr u32 TIMEOUT_SECONDS = 30;
 | 
			
		||||
constexpr u64 VFS_COPY_BLOCK_SIZE = 1ull << 24; // 4MB
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
std::string GetBINFilePath(u64 title_id) {
 | 
			
		||||
    return fmt::format("{}bcat/{:016X}/launchparam.bin",
 | 
			
		||||
                       FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GetZIPFilePath(u64 title_id) {
 | 
			
		||||
    return fmt::format("{}bcat/{:016X}/data.zip",
 | 
			
		||||
                       FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If the error is something the user should know about (build ID mismatch, bad client version),
 | 
			
		||||
// display an error.
 | 
			
		||||
void HandleDownloadDisplayResult(DownloadResult res) {
 | 
			
		||||
    if (res == DownloadResult::Success || res == DownloadResult::NoResponse ||
 | 
			
		||||
        res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError ||
 | 
			
		||||
        res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()};
 | 
			
		||||
    frontend.error->ShowCustomErrorText(
 | 
			
		||||
        ResultCode(-1), "There was an error while attempting to use Boxcat.",
 | 
			
		||||
        DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
 | 
			
		||||
                        std::string_view dir_name, ProgressServiceBackend& progress,
 | 
			
		||||
                        std::size_t block_size = 0x1000) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
        return false;
 | 
			
		||||
    if (!dest->Resize(src->GetSize()))
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> temp(std::min(block_size, src->GetSize()));
 | 
			
		||||
    for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
 | 
			
		||||
        const auto read = std::min(block_size, src->GetSize() - i);
 | 
			
		||||
 | 
			
		||||
        if (src->Read(temp.data(), read, i) != read) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (dest->Write(temp.data(), read, i) != read) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        progress.UpdateFileProgress(i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    progress.FinishDownloadingFile();
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
 | 
			
		||||
                               ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    for (const auto& file : src->GetFiles()) {
 | 
			
		||||
        const auto out_file = VfsCreateFileWrap(dest, file->GetName());
 | 
			
		||||
        if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    progress.CommitDirectory(src->GetName());
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
 | 
			
		||||
                         ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
 | 
			
		||||
    if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    for (const auto& dir : src->GetSubdirectories()) {
 | 
			
		||||
        const auto out = dest->CreateSubdirectory(dir->GetName());
 | 
			
		||||
        if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
class Boxcat::Client {
 | 
			
		||||
public:
 | 
			
		||||
    Client(std::string path, u64 title_id, u64 build_id)
 | 
			
		||||
        : path(std::move(path)), title_id(title_id), build_id(build_id) {}
 | 
			
		||||
 | 
			
		||||
    DownloadResult DownloadDataZip() {
 | 
			
		||||
        return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS,
 | 
			
		||||
                                "application/zip");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DownloadResult DownloadLaunchParam() {
 | 
			
		||||
        return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id),
 | 
			
		||||
                                TIMEOUT_SECONDS / 3, "application/octet-stream");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
 | 
			
		||||
                                    const std::string& content_type_name) {
 | 
			
		||||
        if (client == nullptr) {
 | 
			
		||||
            client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        httplib::Headers headers{
 | 
			
		||||
            {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
 | 
			
		||||
            {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
 | 
			
		||||
            {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)},
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (FileUtil::Exists(path)) {
 | 
			
		||||
            FileUtil::IOFile file{path, "rb"};
 | 
			
		||||
            if (file.IsOpen()) {
 | 
			
		||||
                std::vector<u8> bytes(file.GetSize());
 | 
			
		||||
                file.ReadBytes(bytes.data(), bytes.size());
 | 
			
		||||
                const auto digest = DigestFile(bytes);
 | 
			
		||||
                headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto response = client->Get(resolved_path.c_str(), headers);
 | 
			
		||||
        if (response == nullptr)
 | 
			
		||||
            return DownloadResult::NoResponse;
 | 
			
		||||
 | 
			
		||||
        if (response->status == static_cast<int>(ResponseStatus::NoUpdate))
 | 
			
		||||
            return DownloadResult::Success;
 | 
			
		||||
        if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
 | 
			
		||||
            return DownloadResult::BadClientVersion;
 | 
			
		||||
        if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId))
 | 
			
		||||
            return DownloadResult::NoMatchTitleId;
 | 
			
		||||
        if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId))
 | 
			
		||||
            return DownloadResult::NoMatchBuildId;
 | 
			
		||||
        if (response->status != static_cast<int>(ResponseStatus::Ok))
 | 
			
		||||
            return DownloadResult::GeneralWebError;
 | 
			
		||||
 | 
			
		||||
        const auto content_type = response->headers.find("content-type");
 | 
			
		||||
        if (content_type == response->headers.end() ||
 | 
			
		||||
            content_type->second.find(content_type_name) == std::string::npos) {
 | 
			
		||||
            return DownloadResult::InvalidContentType;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FileUtil::CreateFullPath(path);
 | 
			
		||||
        FileUtil::IOFile file{path, "wb"};
 | 
			
		||||
        if (!file.IsOpen())
 | 
			
		||||
            return DownloadResult::GeneralFSError;
 | 
			
		||||
        if (!file.Resize(response->body.size()))
 | 
			
		||||
            return DownloadResult::GeneralFSError;
 | 
			
		||||
        if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size())
 | 
			
		||||
            return DownloadResult::GeneralFSError;
 | 
			
		||||
 | 
			
		||||
        return DownloadResult::Success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    using Digest = std::array<u8, 0x20>;
 | 
			
		||||
    static Digest DigestFile(std::vector<u8> bytes) {
 | 
			
		||||
        Digest out{};
 | 
			
		||||
        mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0);
 | 
			
		||||
        return out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<httplib::Client> client;
 | 
			
		||||
    std::string path;
 | 
			
		||||
    u64 title_id;
 | 
			
		||||
    u64 build_id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
 | 
			
		||||
 | 
			
		||||
Boxcat::~Boxcat() = default;
 | 
			
		||||
 | 
			
		||||
void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
 | 
			
		||||
                         ProgressServiceBackend& progress,
 | 
			
		||||
                         std::optional<std::string> dir_name = {}) {
 | 
			
		||||
    progress.SetNeedHLELock(true);
 | 
			
		||||
 | 
			
		||||
    if (Settings::values.bcat_boxcat_local) {
 | 
			
		||||
        LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
 | 
			
		||||
        const auto dir = dir_getter(title.title_id);
 | 
			
		||||
        if (dir)
 | 
			
		||||
            progress.SetTotalSize(dir->GetSize());
 | 
			
		||||
        progress.FinishDownload(RESULT_SUCCESS);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto zip_path{GetZIPFilePath(title.title_id)};
 | 
			
		||||
    Boxcat::Client client{zip_path, title.title_id, title.build_id};
 | 
			
		||||
 | 
			
		||||
    progress.StartConnecting();
 | 
			
		||||
 | 
			
		||||
    const auto res = client.DownloadDataZip();
 | 
			
		||||
    if (res != DownloadResult::Success) {
 | 
			
		||||
        LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
 | 
			
		||||
 | 
			
		||||
        if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
 | 
			
		||||
            FileUtil::Delete(zip_path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        HandleDownloadDisplayResult(res);
 | 
			
		||||
        progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    progress.StartProcessingDataList();
 | 
			
		||||
 | 
			
		||||
    FileUtil::IOFile zip{zip_path, "rb"};
 | 
			
		||||
    const auto size = zip.GetSize();
 | 
			
		||||
    std::vector<u8> bytes(size);
 | 
			
		||||
    if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
 | 
			
		||||
        LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
 | 
			
		||||
        progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
 | 
			
		||||
    if (extracted == nullptr) {
 | 
			
		||||
        LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
 | 
			
		||||
        progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dir_name == std::nullopt) {
 | 
			
		||||
        progress.SetTotalSize(extracted->GetSize());
 | 
			
		||||
 | 
			
		||||
        const auto target_dir = dir_getter(title.title_id);
 | 
			
		||||
        if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
 | 
			
		||||
            progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        const auto target_dir = dir_getter(title.title_id);
 | 
			
		||||
        if (target_dir == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
 | 
			
		||||
            progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto target_sub = target_dir->GetSubdirectory(*dir_name);
 | 
			
		||||
        const auto source_sub = extracted->GetSubdirectory(*dir_name);
 | 
			
		||||
 | 
			
		||||
        progress.SetTotalSize(source_sub->GetSize());
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> filenames;
 | 
			
		||||
        {
 | 
			
		||||
            const auto files = target_sub->GetFiles();
 | 
			
		||||
            std::transform(files.begin(), files.end(), std::back_inserter(filenames),
 | 
			
		||||
                           [](const auto& vfile) { return vfile->GetName(); });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const auto& filename : filenames) {
 | 
			
		||||
            VfsDeleteFileWrap(target_sub, filename);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (target_sub == nullptr || source_sub == nullptr ||
 | 
			
		||||
            !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
 | 
			
		||||
            progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    progress.FinishDownload(RESULT_SUCCESS);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
 | 
			
		||||
    is_syncing.exchange(true);
 | 
			
		||||
    std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
 | 
			
		||||
        .detach();
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
 | 
			
		||||
                                  ProgressServiceBackend& progress) {
 | 
			
		||||
    is_syncing.exchange(true);
 | 
			
		||||
    std::thread(
 | 
			
		||||
        [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
 | 
			
		||||
        .detach();
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Boxcat::Clear(u64 title_id) {
 | 
			
		||||
    if (Settings::values.bcat_boxcat_local) {
 | 
			
		||||
        LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear.");
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto dir = dir_getter(title_id);
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> dirnames;
 | 
			
		||||
 | 
			
		||||
    for (const auto& subdir : dir->GetSubdirectories())
 | 
			
		||||
        dirnames.push_back(subdir->GetName());
 | 
			
		||||
 | 
			
		||||
    for (const auto& subdir : dirnames) {
 | 
			
		||||
        if (!dir->DeleteSubdirectoryRecursive(subdir))
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
 | 
			
		||||
              Common::HexToString(passphrase));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) {
 | 
			
		||||
    const auto path{GetBINFilePath(title.title_id)};
 | 
			
		||||
 | 
			
		||||
    if (Settings::values.bcat_boxcat_local) {
 | 
			
		||||
        LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
 | 
			
		||||
    } else {
 | 
			
		||||
        Boxcat::Client client{path, title.title_id, title.build_id};
 | 
			
		||||
 | 
			
		||||
        const auto res = client.DownloadLaunchParam();
 | 
			
		||||
        if (res != DownloadResult::Success) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
 | 
			
		||||
 | 
			
		||||
            if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) {
 | 
			
		||||
                FileUtil::Delete(path);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HandleDownloadDisplayResult(res);
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileUtil::IOFile bin{path, "rb"};
 | 
			
		||||
    const auto size = bin.GetSize();
 | 
			
		||||
    std::vector<u8> bytes(size);
 | 
			
		||||
    if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
 | 
			
		||||
        LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!",
 | 
			
		||||
                  path);
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return bytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
 | 
			
		||||
                                       std::map<std::string, EventStatus>& games) {
 | 
			
		||||
    httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT),
 | 
			
		||||
                              static_cast<int>(TIMEOUT_SECONDS)};
 | 
			
		||||
 | 
			
		||||
    httplib::Headers headers{
 | 
			
		||||
        {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
 | 
			
		||||
        {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
 | 
			
		||||
    if (response == nullptr)
 | 
			
		||||
        return StatusResult::Offline;
 | 
			
		||||
 | 
			
		||||
    if (response->status == static_cast<int>(ResponseStatus::BadClientVersion))
 | 
			
		||||
        return StatusResult::BadClientVersion;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        nlohmann::json json = nlohmann::json::parse(response->body);
 | 
			
		||||
 | 
			
		||||
        if (!json["online"].get<bool>())
 | 
			
		||||
            return StatusResult::Offline;
 | 
			
		||||
 | 
			
		||||
        if (json["global"].is_null())
 | 
			
		||||
            global = std::nullopt;
 | 
			
		||||
        else
 | 
			
		||||
            global = json["global"].get<std::string>();
 | 
			
		||||
 | 
			
		||||
        if (json["games"].is_array()) {
 | 
			
		||||
            for (const auto object : json["games"]) {
 | 
			
		||||
                if (object.is_object() && object.find("name") != object.end()) {
 | 
			
		||||
                    EventStatus detail{};
 | 
			
		||||
                    if (object["header"].is_string()) {
 | 
			
		||||
                        detail.header = object["header"].get<std::string>();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        detail.header = std::nullopt;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (object["footer"].is_string()) {
 | 
			
		||||
                        detail.footer = object["footer"].get<std::string>();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        detail.footer = std::nullopt;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (object["events"].is_array()) {
 | 
			
		||||
                        for (const auto& event : object["events"]) {
 | 
			
		||||
                            if (!event.is_string())
 | 
			
		||||
                                continue;
 | 
			
		||||
                            detail.events.push_back(event.get<std::string>());
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    games.insert_or_assign(object["name"], std::move(detail));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return StatusResult::Success;
 | 
			
		||||
    } catch (const nlohmann::json::parse_error& e) {
 | 
			
		||||
        return StatusResult::ParseError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Service::BCAT
 | 
			
		||||
							
								
								
									
										58
									
								
								src/core/hle/service/bcat/backend/boxcat.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/hle/service/bcat/backend/boxcat.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
// Copyright 2019 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include "core/hle/service/bcat/backend/backend.h"
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
struct EventStatus {
 | 
			
		||||
    std::optional<std::string> header;
 | 
			
		||||
    std::optional<std::string> footer;
 | 
			
		||||
    std::vector<std::string> events;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and
 | 
			
		||||
/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
 | 
			
		||||
class Boxcat final : public Backend {
 | 
			
		||||
    friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
 | 
			
		||||
                                    ProgressServiceBackend& progress,
 | 
			
		||||
                                    std::optional<std::string> dir_name);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit Boxcat(DirectoryGetter getter);
 | 
			
		||||
    ~Boxcat() override;
 | 
			
		||||
 | 
			
		||||
    bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
 | 
			
		||||
    bool SynchronizeDirectory(TitleIDVersion title, std::string name,
 | 
			
		||||
                              ProgressServiceBackend& progress) override;
 | 
			
		||||
 | 
			
		||||
    bool Clear(u64 title_id) override;
 | 
			
		||||
 | 
			
		||||
    void SetPassphrase(u64 title_id, const Passphrase& passphrase) override;
 | 
			
		||||
 | 
			
		||||
    std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override;
 | 
			
		||||
 | 
			
		||||
    enum class StatusResult {
 | 
			
		||||
        Success,
 | 
			
		||||
        Offline,
 | 
			
		||||
        ParseError,
 | 
			
		||||
        BadClientVersion,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static StatusResult GetStatus(std::optional<std::string>& global,
 | 
			
		||||
                                  std::map<std::string, EventStatus>& games);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::atomic_bool is_syncing{false};
 | 
			
		||||
 | 
			
		||||
    class Client;
 | 
			
		||||
    std::unique_ptr<Client> client;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Service::BCAT
 | 
			
		||||
@@ -6,11 +6,15 @@
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
BCAT::BCAT(std::shared_ptr<Module> module, const char* name)
 | 
			
		||||
    : Module::Interface(std::move(module), name) {
 | 
			
		||||
BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name)
 | 
			
		||||
    : Module::Interface(std::move(module), fsc, name) {
 | 
			
		||||
    // clang-format off
 | 
			
		||||
    static const FunctionInfo functions[] = {
 | 
			
		||||
        {0, &BCAT::CreateBcatService, "CreateBcatService"},
 | 
			
		||||
        {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"},
 | 
			
		||||
        {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"},
 | 
			
		||||
    };
 | 
			
		||||
    // clang-format on
 | 
			
		||||
    RegisterHandlers(functions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,8 @@ namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
class BCAT final : public Module::Interface {
 | 
			
		||||
public:
 | 
			
		||||
    explicit BCAT(std::shared_ptr<Module> module, const char* name);
 | 
			
		||||
    explicit BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
 | 
			
		||||
                  const char* name);
 | 
			
		||||
    ~BCAT() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,34 +2,254 @@
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <mbedtls/md5.h>
 | 
			
		||||
#include "backend/boxcat.h"
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/hle/ipc_helpers.h"
 | 
			
		||||
#include "core/hle/kernel/process.h"
 | 
			
		||||
#include "core/hle/kernel/readable_event.h"
 | 
			
		||||
#include "core/hle/kernel/writable_event.h"
 | 
			
		||||
#include "core/hle/service/bcat/backend/backend.h"
 | 
			
		||||
#include "core/hle/service/bcat/bcat.h"
 | 
			
		||||
#include "core/hle/service/bcat/module.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
 | 
			
		||||
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1};
 | 
			
		||||
constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2};
 | 
			
		||||
constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6};
 | 
			
		||||
constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7};
 | 
			
		||||
 | 
			
		||||
// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files
 | 
			
		||||
// and if any of them have a non-zero result it just forwards that result. This is the FS error code
 | 
			
		||||
// for permission denied, which is the closest approximation of this scenario.
 | 
			
		||||
constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
 | 
			
		||||
 | 
			
		||||
using BCATDigest = std::array<u8, 0x10>;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
u64 GetCurrentBuildID() {
 | 
			
		||||
    const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID();
 | 
			
		||||
    u64 out{};
 | 
			
		||||
    std::memcpy(&out, id.data(), sizeof(u64));
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The digest is only used to determine if a file is unique compared to others of the same name.
 | 
			
		||||
// Since the algorithm isn't ever checked in game, MD5 is safe.
 | 
			
		||||
BCATDigest DigestFile(const FileSys::VirtualFile& file) {
 | 
			
		||||
    BCATDigest out{};
 | 
			
		||||
    const auto bytes = file->ReadAllBytes();
 | 
			
		||||
    mbedtls_md5(bytes.data(), bytes.size(), out.data());
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// For a name to be valid it must be non-empty, must have a null terminating character as the final
 | 
			
		||||
// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if
 | 
			
		||||
// file.
 | 
			
		||||
bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name,
 | 
			
		||||
                             char match_char) {
 | 
			
		||||
    const auto null_chars = std::count(name.begin(), name.end(), 0);
 | 
			
		||||
    const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) {
 | 
			
		||||
        return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0';
 | 
			
		||||
    });
 | 
			
		||||
    if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') {
 | 
			
		||||
        LOG_ERROR(Service_BCAT, "Name passed was invalid!");
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(ERROR_INVALID_ARGUMENT);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
 | 
			
		||||
    return VerifyNameValidInternal(ctx, name, '-');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
 | 
			
		||||
    return VerifyNameValidInternal(ctx, name, '.');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
struct DeliveryCacheDirectoryEntry {
 | 
			
		||||
    FileName name;
 | 
			
		||||
    u64 size;
 | 
			
		||||
    BCATDigest digest;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
 | 
			
		||||
public:
 | 
			
		||||
    IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event,
 | 
			
		||||
                                  const DeliveryCacheProgressImpl& impl)
 | 
			
		||||
        : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
 | 
			
		||||
        // clang-format off
 | 
			
		||||
        static const FunctionInfo functions[] = {
 | 
			
		||||
            {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
 | 
			
		||||
            {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"},
 | 
			
		||||
        };
 | 
			
		||||
        // clang-format on
 | 
			
		||||
 | 
			
		||||
        RegisterHandlers(functions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void GetEvent(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 1};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushCopyObjects(event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void GetImpl(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl));
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Kernel::SharedPtr<Kernel::ReadableEvent> event;
 | 
			
		||||
    const DeliveryCacheProgressImpl& impl;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IBcatService final : public ServiceFramework<IBcatService> {
 | 
			
		||||
public:
 | 
			
		||||
    IBcatService() : ServiceFramework("IBcatService") {
 | 
			
		||||
    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"},
 | 
			
		||||
            {30100, nullptr, "SetPassphrase"},
 | 
			
		||||
            {30100, &IBcatService::SetPassphrase, "SetPassphrase"},
 | 
			
		||||
            {30200, nullptr, "RegisterBackgroundDeliveryTask"},
 | 
			
		||||
            {30201, nullptr, "UnregisterBackgroundDeliveryTask"},
 | 
			
		||||
            {30202, nullptr, "BlockDeliveryTask"},
 | 
			
		||||
            {30203, nullptr, "UnblockDeliveryTask"},
 | 
			
		||||
            {90100, nullptr, "EnumerateBackgroundDeliveryTask"},
 | 
			
		||||
            {90200, nullptr, "GetDeliveryList"},
 | 
			
		||||
            {90201, nullptr, "ClearDeliveryCacheStorage"},
 | 
			
		||||
            {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"},
 | 
			
		||||
            {90300, nullptr, "GetPushNotificationLog"},
 | 
			
		||||
        };
 | 
			
		||||
        // clang-format on
 | 
			
		||||
        RegisterHandlers(functions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    enum class SyncType {
 | 
			
		||||
        Normal,
 | 
			
		||||
        Directory,
 | 
			
		||||
        Count,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
 | 
			
		||||
        auto& backend{progress.at(static_cast<std::size_t>(type))};
 | 
			
		||||
        return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
 | 
			
		||||
                                                               backend.GetImpl());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
 | 
			
		||||
                            progress.at(static_cast<std::size_t>(SyncType::Normal)));
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushIpcInterface(CreateProgressService(SyncType::Normal));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto name_raw = rp.PopRaw<DirectoryName>();
 | 
			
		||||
        const auto name =
 | 
			
		||||
            Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, name={}", name);
 | 
			
		||||
 | 
			
		||||
        backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
 | 
			
		||||
                                     name,
 | 
			
		||||
                                     progress.at(static_cast<std::size_t>(SyncType::Directory)));
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushIpcInterface(CreateProgressService(SyncType::Directory));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetPassphrase(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto title_id = rp.PopRaw<u64>();
 | 
			
		||||
 | 
			
		||||
        const auto passphrase_raw = ctx.ReadBuffer();
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
 | 
			
		||||
                  Common::HexToString(passphrase_raw));
 | 
			
		||||
 | 
			
		||||
        if (title_id == 0) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Invalid title ID!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_INVALID_ARGUMENT);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (passphrase_raw.size() > 0x40) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Passphrase too large!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_INVALID_ARGUMENT);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Passphrase passphrase{};
 | 
			
		||||
        std::memcpy(passphrase.data(), passphrase_raw.data(),
 | 
			
		||||
                    std::min(passphrase.size(), passphrase_raw.size()));
 | 
			
		||||
 | 
			
		||||
        backend.SetPassphrase(title_id, passphrase);
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto title_id = rp.PopRaw<u64>();
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
 | 
			
		||||
 | 
			
		||||
        if (title_id == 0) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Invalid title ID!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_INVALID_ARGUMENT);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!backend.Clear(title_id)) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_FAILED_CLEAR_CACHE);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Backend& backend;
 | 
			
		||||
 | 
			
		||||
    std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
 | 
			
		||||
        ProgressServiceBackend{"Normal"},
 | 
			
		||||
        ProgressServiceBackend{"Directory"},
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
@@ -37,20 +257,331 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.PushIpcInterface<IBcatService>();
 | 
			
		||||
    rb.PushIpcInterface<IBcatService>(*backend);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
 | 
			
		||||
    : ServiceFramework(name), module(std::move(module)) {}
 | 
			
		||||
class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
 | 
			
		||||
public:
 | 
			
		||||
    IDeliveryCacheFileService(FileSys::VirtualDir root_)
 | 
			
		||||
        : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
 | 
			
		||||
        // clang-format off
 | 
			
		||||
        static const FunctionInfo functions[] = {
 | 
			
		||||
            {0, &IDeliveryCacheFileService::Open, "Open"},
 | 
			
		||||
            {1, &IDeliveryCacheFileService::Read, "Read"},
 | 
			
		||||
            {2, &IDeliveryCacheFileService::GetSize, "GetSize"},
 | 
			
		||||
            {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"},
 | 
			
		||||
        };
 | 
			
		||||
        // clang-format on
 | 
			
		||||
 | 
			
		||||
        RegisterHandlers(functions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void Open(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto dir_name_raw = rp.PopRaw<DirectoryName>();
 | 
			
		||||
        const auto file_name_raw = rp.PopRaw<FileName>();
 | 
			
		||||
 | 
			
		||||
        const auto dir_name =
 | 
			
		||||
            Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size());
 | 
			
		||||
        const auto file_name =
 | 
			
		||||
            Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size());
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name);
 | 
			
		||||
 | 
			
		||||
        if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (current_file != nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_ENTITY_ALREADY_OPEN);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto dir = root->GetSubdirectory(dir_name);
 | 
			
		||||
 | 
			
		||||
        if (dir == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name);
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_FAILED_OPEN_ENTITY);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        current_file = dir->GetFile(file_name);
 | 
			
		||||
 | 
			
		||||
        if (current_file == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name);
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_FAILED_OPEN_ENTITY);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Read(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto offset{rp.PopRaw<u64>()};
 | 
			
		||||
 | 
			
		||||
        auto size = ctx.GetWriteBufferSize();
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size);
 | 
			
		||||
 | 
			
		||||
        if (current_file == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "There is no file currently open!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_NO_OPEN_ENTITY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        size = std::min<u64>(current_file->GetSize() - offset, size);
 | 
			
		||||
        const auto buffer = current_file->ReadBytes(size, offset);
 | 
			
		||||
        ctx.WriteBuffer(buffer);
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 4};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u64>(buffer.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void GetSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        if (current_file == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "There is no file currently open!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_NO_OPEN_ENTITY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 4};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u64>(current_file->GetSize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void GetDigest(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        if (current_file == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "There is no file currently open!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_NO_OPEN_ENTITY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 6};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushRaw(DigestFile(current_file));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileSys::VirtualDir root;
 | 
			
		||||
    FileSys::VirtualFile current_file;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IDeliveryCacheDirectoryService final
 | 
			
		||||
    : public ServiceFramework<IDeliveryCacheDirectoryService> {
 | 
			
		||||
public:
 | 
			
		||||
    IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
 | 
			
		||||
        : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
 | 
			
		||||
        // clang-format off
 | 
			
		||||
        static const FunctionInfo functions[] = {
 | 
			
		||||
            {0, &IDeliveryCacheDirectoryService::Open, "Open"},
 | 
			
		||||
            {1, &IDeliveryCacheDirectoryService::Read, "Read"},
 | 
			
		||||
            {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"},
 | 
			
		||||
        };
 | 
			
		||||
        // clang-format on
 | 
			
		||||
 | 
			
		||||
        RegisterHandlers(functions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void Open(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        IPC::RequestParser rp{ctx};
 | 
			
		||||
        const auto name_raw = rp.PopRaw<DirectoryName>();
 | 
			
		||||
        const auto name =
 | 
			
		||||
            Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size());
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, name={}", name);
 | 
			
		||||
 | 
			
		||||
        if (!VerifyNameValidDir(ctx, name_raw))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (current_dir != nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_ENTITY_ALREADY_OPEN);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        current_dir = root->GetSubdirectory(name);
 | 
			
		||||
 | 
			
		||||
        if (current_dir == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name);
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_FAILED_OPEN_ENTITY);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Read(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
 | 
			
		||||
 | 
			
		||||
        if (current_dir == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "There is no open directory!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_NO_OPEN_ENTITY);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto files = current_dir->GetFiles();
 | 
			
		||||
        write_size = std::min<u64>(write_size, files.size());
 | 
			
		||||
        std::vector<DeliveryCacheDirectoryEntry> entries(write_size);
 | 
			
		||||
        std::transform(
 | 
			
		||||
            files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) {
 | 
			
		||||
                FileName name{};
 | 
			
		||||
                std::memcpy(name.data(), file->GetName().data(),
 | 
			
		||||
                            std::min(file->GetName().size(), name.size()));
 | 
			
		||||
                return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)};
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        ctx.WriteBuffer(entries);
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void GetCount(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        if (current_dir == nullptr) {
 | 
			
		||||
            LOG_ERROR(Service_BCAT, "There is no open directory!");
 | 
			
		||||
            IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
            rb.Push(ERROR_NO_OPEN_ENTITY);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto files = current_dir->GetFiles();
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u32>(files.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileSys::VirtualDir root;
 | 
			
		||||
    FileSys::VirtualDir current_dir;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
 | 
			
		||||
public:
 | 
			
		||||
    IDeliveryCacheStorageService(FileSys::VirtualDir root_)
 | 
			
		||||
        : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
 | 
			
		||||
        // clang-format off
 | 
			
		||||
        static const FunctionInfo functions[] = {
 | 
			
		||||
            {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
 | 
			
		||||
            {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"},
 | 
			
		||||
            {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"},
 | 
			
		||||
        };
 | 
			
		||||
        // clang-format on
 | 
			
		||||
 | 
			
		||||
        RegisterHandlers(functions);
 | 
			
		||||
 | 
			
		||||
        for (const auto& subdir : root->GetSubdirectories()) {
 | 
			
		||||
            DirectoryName name{};
 | 
			
		||||
            std::memcpy(name.data(), subdir->GetName().data(),
 | 
			
		||||
                        std::min(sizeof(DirectoryName) - 1, subdir->GetName().size()));
 | 
			
		||||
            entries.push_back(name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void CreateFileService(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushIpcInterface<IDeliveryCacheFileService>(root);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
 | 
			
		||||
 | 
			
		||||
        LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
 | 
			
		||||
 | 
			
		||||
        size = std::min<u64>(size, entries.size() - next_read_index);
 | 
			
		||||
        ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName));
 | 
			
		||||
        next_read_index += size;
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u32>(size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    FileSys::VirtualDir root;
 | 
			
		||||
    std::vector<DirectoryName> entries;
 | 
			
		||||
    u64 next_read_index = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called");
 | 
			
		||||
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.PushIpcInterface<IDeliveryCacheStorageService>(
 | 
			
		||||
        fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
 | 
			
		||||
    Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx};
 | 
			
		||||
    const auto title_id = rp.PopRaw<u64>();
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
 | 
			
		||||
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) {
 | 
			
		||||
    const auto backend = Settings::values.bcat_backend;
 | 
			
		||||
 | 
			
		||||
#ifdef YUZU_ENABLE_BOXCAT
 | 
			
		||||
    if (backend == "boxcat")
 | 
			
		||||
        return std::make_unique<Boxcat>(std::move(getter));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    return std::make_unique<NullBackend>(std::move(getter));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
 | 
			
		||||
                             const char* name)
 | 
			
		||||
    : ServiceFramework(name), module(std::move(module)), fsc(fsc),
 | 
			
		||||
      backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {}
 | 
			
		||||
 | 
			
		||||
Module::Interface::~Interface() = default;
 | 
			
		||||
 | 
			
		||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
 | 
			
		||||
void InstallInterfaces(Core::System& system) {
 | 
			
		||||
    auto module = std::make_shared<Module>();
 | 
			
		||||
    std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager);
 | 
			
		||||
    std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager);
 | 
			
		||||
    std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager);
 | 
			
		||||
    std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager);
 | 
			
		||||
    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:a")
 | 
			
		||||
        ->InstallAsService(system.ServiceManager());
 | 
			
		||||
    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m")
 | 
			
		||||
        ->InstallAsService(system.ServiceManager());
 | 
			
		||||
    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u")
 | 
			
		||||
        ->InstallAsService(system.ServiceManager());
 | 
			
		||||
    std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:s")
 | 
			
		||||
        ->InstallAsService(system.ServiceManager());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Service::BCAT
 | 
			
		||||
 
 | 
			
		||||
@@ -6,23 +6,39 @@
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
 | 
			
		||||
namespace Service::BCAT {
 | 
			
		||||
namespace Service {
 | 
			
		||||
 | 
			
		||||
namespace FileSystem {
 | 
			
		||||
class FileSystemController;
 | 
			
		||||
} // namespace FileSystem
 | 
			
		||||
 | 
			
		||||
namespace BCAT {
 | 
			
		||||
 | 
			
		||||
class Backend;
 | 
			
		||||
 | 
			
		||||
class Module final {
 | 
			
		||||
public:
 | 
			
		||||
    class Interface : public ServiceFramework<Interface> {
 | 
			
		||||
    public:
 | 
			
		||||
        explicit Interface(std::shared_ptr<Module> module, const char* name);
 | 
			
		||||
        explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc,
 | 
			
		||||
                           const char* name);
 | 
			
		||||
        ~Interface() override;
 | 
			
		||||
 | 
			
		||||
        void CreateBcatService(Kernel::HLERequestContext& ctx);
 | 
			
		||||
        void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx);
 | 
			
		||||
        void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
    protected:
 | 
			
		||||
        FileSystem::FileSystemController& fsc;
 | 
			
		||||
 | 
			
		||||
        std::shared_ptr<Module> module;
 | 
			
		||||
        std::unique_ptr<Backend> backend;
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// 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
 | 
			
		||||
 
 | 
			
		||||
@@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id)
 | 
			
		||||
    return bis_factory->GetModificationDumpRoot(title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
 | 
			
		||||
    LOG_TRACE(Service_FS, "Opening BCAT root for tid={:016X}", title_id);
 | 
			
		||||
 | 
			
		||||
    if (bis_factory == nullptr)
 | 
			
		||||
        return nullptr;
 | 
			
		||||
 | 
			
		||||
    return bis_factory->GetBCATDirectory(title_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
 | 
			
		||||
    if (overwrite) {
 | 
			
		||||
        bis_factory = nullptr;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,13 @@
 | 
			
		||||
 | 
			
		||||
namespace Service::NIFM {
 | 
			
		||||
 | 
			
		||||
enum class RequestState : u32 {
 | 
			
		||||
    NotSubmitted = 1,
 | 
			
		||||
    Error = 1, ///< The duplicate 1 is intentional; it means both not submitted and error on HW.
 | 
			
		||||
    Pending = 2,
 | 
			
		||||
    Connected = 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class IScanRequest final : public ServiceFramework<IScanRequest> {
 | 
			
		||||
public:
 | 
			
		||||
    explicit IScanRequest() : ServiceFramework("IScanRequest") {
 | 
			
		||||
@@ -81,7 +88,7 @@ private:
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u32>(0);
 | 
			
		||||
        rb.PushEnum(RequestState::Connected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void GetResult(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
@@ -189,14 +196,14 @@ private:
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u8>(0);
 | 
			
		||||
        rb.Push<u8>(1);
 | 
			
		||||
    }
 | 
			
		||||
    void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_WARNING(Service_NIFM, "(STUBBED) called");
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 3};
 | 
			
		||||
        rb.Push(RESULT_SUCCESS);
 | 
			
		||||
        rb.Push<u8>(0);
 | 
			
		||||
        rb.Push<u8>(1);
 | 
			
		||||
    }
 | 
			
		||||
    Core::System& system;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
 | 
			
		||||
    AOC::InstallInterfaces(*sm, system);
 | 
			
		||||
    APM::InstallInterfaces(system);
 | 
			
		||||
    Audio::InstallInterfaces(*sm, system);
 | 
			
		||||
    BCAT::InstallInterfaces(*sm);
 | 
			
		||||
    BCAT::InstallInterfaces(system);
 | 
			
		||||
    BPC::InstallInterfaces(*sm);
 | 
			
		||||
    BtDrv::InstallInterfaces(*sm, system);
 | 
			
		||||
    BTM::InstallInterfaces(*sm, system);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user