diff options
Diffstat (limited to 'src/core')
69 files changed, 2125 insertions, 288 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a6b56c9c6..3b1d72cf9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,3 +1,9 @@ +if (YUZU_ENABLE_BOXCAT) + set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h) +else() + set(BCAT_BOXCAT_ADDITIONAL_SOURCES) +endif() + add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp @@ -82,6 +88,8 @@ add_library(core STATIC file_sys/vfs_concat.h file_sys/vfs_layered.cpp file_sys/vfs_layered.h + file_sys/vfs_libzip.cpp + file_sys/vfs_libzip.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp @@ -241,6 +249,9 @@ add_library(core STATIC hle/service/audio/errors.h hle/service/audio/hwopus.cpp hle/service/audio/hwopus.h + hle/service/bcat/backend/backend.cpp + hle/service/bcat/backend/backend.h + ${BCAT_BOXCAT_ADDITIONAL_SOURCES} hle/service/bcat/bcat.cpp hle/service/bcat/bcat.h hle/service/bcat/module.cpp @@ -324,6 +335,8 @@ add_library(core STATIC hle/service/ldr/ldr.h hle/service/lm/lm.cpp hle/service/lm/lm.h + hle/service/lm/manager.cpp + hle/service/lm/manager.h hle/service/mig/mig.cpp hle/service/mig/mig.h hle/service/mii/mii.cpp @@ -499,6 +512,15 @@ create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives) + +if (YUZU_ENABLE_BOXCAT) + get_directory_property(OPENSSL_LIBS + DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl + DEFINITION OPENSSL_LIBS) + target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT) + target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip) +endif() + if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) target_link_libraries(core PRIVATE web_service) diff --git a/src/core/core.cpp b/src/core/core.cpp index 92ba42fb9..4d0ac72a5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -35,6 +35,7 @@ #include "core/hle/service/apm/controller.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/glue/manager.h" +#include "core/hle/service/lm/manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" @@ -111,7 +112,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, } struct System::Impl { explicit Impl(System& system) - : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} + : kernel{system}, fs_controller{system}, cpu_core_manager{system}, + applet_manager{system}, reporter{system} {} Cpu& CurrentCpuCore() { return cpu_core_manager.GetCurrentCore(); @@ -249,6 +251,8 @@ struct System::Impl { telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", perf_stats->GetMeanFrametime()); + lm_manager.Flush(); + is_powered_on = false; exit_lock = false; @@ -337,8 +341,10 @@ struct System::Impl { bool is_powered_on = false; bool exit_lock = false; + Reporter reporter; std::unique_ptr<Memory::CheatEngine> cheat_engine; std::unique_ptr<Tools::Freezer> memory_freezer; + std::array<u8, 0x20> build_id{}; /// Frontend applets Service::AM::Applets::AppletManager applet_manager; @@ -346,8 +352,9 @@ struct System::Impl { /// APM (Performance) services Service::APM::Controller apm_controller{core_timing}; - /// Glue services + /// Service State Service::Glue::ARPManager arp_manager; + Service::LM::Manager lm_manager{reporter}; /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; @@ -355,8 +362,6 @@ struct System::Impl { /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; - Reporter reporter; - ResultStatus status = ResultStatus::Success; std::string status_details = ""; @@ -632,6 +637,14 @@ const Service::APM::Controller& System::GetAPMController() const { return impl->apm_controller; } +Service::LM::Manager& System::GetLogManager() { + return impl->lm_manager; +} + +const Service::LM::Manager& System::GetLogManager() const { + return impl->lm_manager; +} + void System::SetExitLock(bool locked) { impl->exit_lock = locked; } @@ -640,6 +653,14 @@ bool System::GetExitLock() const { return impl->exit_lock; } +void System::SetCurrentProcessBuildID(const CurrentBuildProcessID& id) { + impl->build_id = id; +} + +const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const { + return impl->build_id; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(*this, emu_window); } diff --git a/src/core/core.h b/src/core/core.h index ff10ebe12..90e7ac607 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -8,7 +8,6 @@ #include <memory> #include <string> -#include <map> #include "common/common_types.h" #include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" @@ -58,6 +57,10 @@ namespace Glue { class ARPManager; } +namespace LM { +class Manager; +} // namespace LM + namespace SM { class ServiceManager; } // namespace SM @@ -98,6 +101,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, class System { public: + using CurrentBuildProcessID = std::array<u8, 0x20>; + System(const System&) = delete; System& operator=(const System&) = delete; @@ -326,10 +331,18 @@ public: const Service::APM::Controller& GetAPMController() const; + Service::LM::Manager& GetLogManager(); + + const Service::LM::Manager& GetLogManager() const; + void SetExitLock(bool locked); bool GetExitLock() const; + void SetCurrentProcessBuildID(const CurrentBuildProcessID& id); + + const CurrentBuildProcessID& GetCurrentProcessBuildID() const; + private: System(); @@ -353,8 +366,4 @@ private: static System s_instance; }; -inline Kernel::Process* CurrentProcess() { - return System::GetInstance().CurrentProcess(); -} - } // namespace Core diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 46aceec3d..222fc95ba 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -423,7 +423,7 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { const auto issuer = ticket.GetData().issuer; - if (issuer == std::array<u8, 0x40>{}) + if (IsAllZeroArray(issuer)) return {}; if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 8f758d6d9..0af44f340 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -136,4 +136,9 @@ u64 BISFactory::GetFullNANDTotalSpace() const { return static_cast<u64>(Settings::values.nand_total_size); } +VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const { + return GetOrCreateDirectoryRelative(nand_root, + fmt::format("/system/save/bcat/{:016X}", title_id)); +} + } // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index bdfe728c9..8f0451c98 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -61,6 +61,8 @@ public: u64 GetUserNANDTotalSpace() const; u64 GetFullNANDTotalSpace() const; + VirtualDir GetBCATDirectory(u64 title_id) const; + private: VirtualDir nand_root; VirtualDir load_root; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 84cd4684c..4bd2e6183 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -35,11 +35,11 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { this->update_raw = std::move(update_raw); } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const { +ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { if (!updatable) return MakeResult<VirtualFile>(file); - const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); + const PatchManager patch_manager(current_process_title_id); return MakeResult<VirtualFile>( patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index da63a313a..c5d40285c 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -33,7 +33,7 @@ public: ~RomFSFactory(); void SetPackedUpdate(VirtualFile update_raw); - ResultVal<VirtualFile> OpenCurrentProcess() const; + ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; private: diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index f77cc02ac..fc8755c78 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -127,8 +127,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ u128 user_id, u64 save_id) { // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should // be interpreted as the title id of the current process. - if (type == SaveDataType::SaveData && title_id == 0) - title_id = Core::CurrentProcess()->GetTitleID(); + if (type == SaveDataType::SaveData && title_id == 0) { + title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + } std::string out = GetSaveDataSpaceIdPath(space); diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp new file mode 100644 index 000000000..8bdaa7e4a --- /dev/null +++ b/src/core/file_sys/vfs_libzip.cpp @@ -0,0 +1,79 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <string> +#include <zip.h> +#include "common/logging/backend.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_libzip.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +VirtualDir ExtractZIP(VirtualFile file) { + zip_error_t error{}; + + const auto data = file->ReadAllBytes(); + std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{ + zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close}; + if (src == nullptr) + return nullptr; + + std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error), + zip_close}; + if (zip == nullptr) + return nullptr; + + std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>(); + + const auto num_entries = zip_get_num_entries(zip.get(), 0); + + zip_stat_t stat{}; + zip_stat_init(&stat); + + for (std::size_t i = 0; i < num_entries; ++i) { + const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat); + if (stat_res == -1) + return nullptr; + + const std::string name(stat.name); + if (name.empty()) + continue; + + if (name.back() != '/') { + std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file{ + zip_fopen_index(zip.get(), i, 0), zip_fclose}; + + std::vector<u8> buf(stat.size); + if (zip_fread(file.get(), buf.data(), buf.size()) != buf.size()) + return nullptr; + + const auto parts = FileUtil::SplitPathComponents(stat.name); + const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back()); + + std::shared_ptr<VectorVfsDirectory> dtrv = out; + for (std::size_t j = 0; j < parts.size() - 1; ++j) { + if (dtrv == nullptr) + return nullptr; + const auto subdir = dtrv->GetSubdirectory(parts[j]); + if (subdir == nullptr) { + const auto temp = std::make_shared<VectorVfsDirectory>( + std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parts[j]); + dtrv->AddDirectory(temp); + dtrv = temp; + } else { + dtrv = std::dynamic_pointer_cast<VectorVfsDirectory>(subdir); + } + } + + if (dtrv == nullptr) + return nullptr; + dtrv->AddFile(new_file); + } + } + + return out; +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_libzip.h b/src/core/file_sys/vfs_libzip.h new file mode 100644 index 000000000..f68af576a --- /dev/null +++ b/src/core/file_sys/vfs_libzip.h @@ -0,0 +1,13 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" + +namespace FileSys { + +VirtualDir ExtractZIP(VirtualFile zip); + +} // namespace FileSys diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index afa812598..db51d722f 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -641,7 +641,8 @@ static void HandleQuery() { strlen("Xfer:features:read:target.xml:")) == 0) { SendReply(target_xml); } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { - const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress(); + const VAddr base_address = + Core::System::GetInstance().CurrentProcess()->VMManager().GetCodeRegionBaseAddress(); std::string buffer = fmt::format("TextSeg={:0x}", base_address); SendReply(buffer.c_str()); } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index bdfaa977f..2cc5d536b 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -103,7 +103,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { return GetCurrentThread(); } else if (handle == CurrentProcess) { - return Core::CurrentProcess(); + return Core::System::GetInstance().CurrentProcess(); } if (!IsValid(handle)) { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 797c9a06f..941ebc93a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -31,6 +31,7 @@ #include "core/hle/service/am/tcap.h" #include "core/hle/service/apm/controller.h" #include "core/hle/service/apm/interface.h" +#include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -46,15 +47,20 @@ constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2}; constexpr ResultCode ERR_NO_MESSAGES{ErrorModule::AM, 0x3}; constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; -constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; +enum class LaunchParameterKind : u32 { + ApplicationSpecific = 1, + AccountPreselectedUser = 2, +}; + +constexpr u32 LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC = 0xC79497CA; -struct LaunchParameters { +struct LaunchParameterAccountPreselectedUser { u32_le magic; u32_le is_account_selected; u128 current_user; INSERT_PADDING_BYTES(0x70); }; -static_assert(sizeof(LaunchParameters) == 0x88); +static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88); IWindowController::IWindowController(Core::System& system_) : ServiceFramework("IWindowController"), system{system_} { @@ -1128,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx } void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_AM, "called"); + IPC::RequestParser rp{ctx}; + const auto kind = rp.PopEnum<LaunchParameterKind>(); - LaunchParameters params{}; + LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind)); - params.magic = POP_LAUNCH_PARAMETER_MAGIC; - params.is_account_selected = 1; + if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { + const auto backend = BCAT::CreateBackendFromSettings( + [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); }); + const auto build_id_full = system.GetCurrentProcessBuildID(); + u64 build_id{}; + std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); - Account::ProfileManager profile_manager{}; - const auto uuid = profile_manager.GetUser(Settings::values.current_user); - ASSERT(uuid); - params.current_user = uuid->uuid; + const auto data = + backend->GetLaunchParameter({system.CurrentProcess()->GetTitleID(), build_id}); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + if (data.has_value()) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorage>(*data); + launch_popped_application_specific = true; + return; + } + } else if (kind == LaunchParameterKind::AccountPreselectedUser && + !launch_popped_account_preselect) { + LaunchParameterAccountPreselectedUser params{}; - rb.Push(RESULT_SUCCESS); + params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; + params.is_account_selected = 1; - std::vector<u8> buffer(sizeof(LaunchParameters)); - std::memcpy(buffer.data(), ¶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( @@ -1165,7 +1200,7 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]); FileSys::SaveDataDescriptor descriptor{}; - descriptor.title_id = Core::CurrentProcess()->GetTitleID(); + descriptor.title_id = system.CurrentProcess()->GetTitleID(); descriptor.user_id = user_id; descriptor.type = FileSys::SaveDataType::SaveData; const auto res = system.GetFileSystemController().CreateSaveData( diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index a3baeb673..ccd053c13 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -147,6 +147,7 @@ private: void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); + Core::System& system; std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::EventPair launchable_event; Kernel::EventPair accumulated_suspended_tick_changed_event; @@ -154,8 +155,6 @@ private: u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; bool is_auto_sleep_disabled = false; - - Core::System& system; }; class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { @@ -255,6 +254,8 @@ private: void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); + bool launch_popped_application_specific = false; + bool launch_popped_account_preselect = false; Kernel::EventPair gpu_error_detected_event; Core::System& system; }; diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index d2e35362f..720fe766f 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -157,6 +157,10 @@ AppletManager::AppletManager(Core::System& system_) : system{system_} {} AppletManager::~AppletManager() = default; +const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { + return frontend; +} + void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { if (set.parental_controls != nullptr) frontend.parental_controls = std::move(set.parental_controls); diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index 764c3418c..226be88b1 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -190,6 +190,8 @@ public: explicit AppletManager(Core::System& system_); ~AppletManager(); + const AppletFrontendSet& GetAppletFrontendSet() const; + void SetAppletFrontendSet(AppletFrontendSet set); void SetDefaultAppletFrontendSet(); void SetDefaultAppletsIfMissing(); diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp index 4376612eb..073d0f6fa 100644 --- a/src/core/hle/service/apm/controller.cpp +++ b/src/core/hle/service/apm/controller.cpp @@ -13,7 +13,7 @@ constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION = PerformanceConfiguration::Config7; Controller::Controller(Core::Timing::CoreTiming& core_timing) - : core_timing(core_timing), configs{ + : core_timing{core_timing}, configs{ {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION}, {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION}, } {} @@ -63,6 +63,7 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa void Controller::SetClockSpeed(u32 mhz) { LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); // TODO(DarkLordZach): Actually signal core_timing to change clock speed. + // TODO(Rodrigo): Remove [[maybe_unused]] when core_timing is used. } } // namespace Service::APM diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h index 8ac80eaea..454caa6eb 100644 --- a/src/core/hle/service/apm/controller.h +++ b/src/core/hle/service/apm/controller.h @@ -50,7 +50,7 @@ enum class PerformanceMode : u8 { // system during times of high load -- this simply maps to different PerformanceConfigs to use. class Controller { public: - Controller(Core::Timing::CoreTiming& core_timing); + explicit Controller(Core::Timing::CoreTiming& core_timing); ~Controller(); void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config); @@ -62,9 +62,9 @@ public: private: void SetClockSpeed(u32 mhz); - std::map<PerformanceMode, PerformanceConfiguration> configs; + [[maybe_unused]] Core::Timing::CoreTiming& core_timing; - Core::Timing::CoreTiming& core_timing; + std::map<PerformanceMode, PerformanceConfiguration> configs; }; } // namespace Service::APM diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index fb84a8f13..9afefb5c6 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -205,7 +205,7 @@ private: AudioCore::StreamPtr stream; std::string device_name; - AudoutParams audio_params{}; + [[maybe_unused]] AudoutParams audio_params {}; /// This is the event handle used to check if the audio buffer was released Kernel::EventPair buffer_event; diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp new file mode 100644 index 000000000..9d6946bc5 --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -0,0 +1,137 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/bcat/backend/backend.h" + +namespace Service::BCAT { + +ProgressServiceBackend::ProgressServiceBackend(std::string_view event_name) { + auto& kernel{Core::System::GetInstance().Kernel()}; + event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, + std::string("ProgressServiceBackend:UpdateEvent:").append(event_name)); +} + +Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const { + return event.readable; +} + +DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() { + return impl; +} + +void ProgressServiceBackend::SetNeedHLELock(bool need) { + need_hle_lock = need; +} + +void ProgressServiceBackend::SetTotalSize(u64 size) { + impl.total_bytes = size; + SignalUpdate(); +} + +void ProgressServiceBackend::StartConnecting() { + impl.status = DeliveryCacheProgressImpl::Status::Connecting; + SignalUpdate(); +} + +void ProgressServiceBackend::StartProcessingDataList() { + impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList; + SignalUpdate(); +} + +void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name, + std::string_view file_name, u64 file_size) { + impl.status = DeliveryCacheProgressImpl::Status::Downloading; + impl.current_downloaded_bytes = 0; + impl.current_total_bytes = file_size; + std::memcpy(impl.current_directory.data(), dir_name.data(), + std::min<u64>(dir_name.size(), 0x31ull)); + std::memcpy(impl.current_file.data(), file_name.data(), + std::min<u64>(file_name.size(), 0x31ull)); + SignalUpdate(); +} + +void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) { + impl.current_downloaded_bytes = downloaded; + SignalUpdate(); +} + +void ProgressServiceBackend::FinishDownloadingFile() { + impl.total_downloaded_bytes += impl.current_total_bytes; + SignalUpdate(); +} + +void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) { + impl.status = DeliveryCacheProgressImpl::Status::Committing; + impl.current_file.fill(0); + impl.current_downloaded_bytes = 0; + impl.current_total_bytes = 0; + std::memcpy(impl.current_directory.data(), dir_name.data(), + std::min<u64>(dir_name.size(), 0x31ull)); + SignalUpdate(); +} + +void ProgressServiceBackend::FinishDownload(ResultCode result) { + impl.total_downloaded_bytes = impl.total_bytes; + impl.status = DeliveryCacheProgressImpl::Status::Done; + impl.result = result; + SignalUpdate(); +} + +void ProgressServiceBackend::SignalUpdate() const { + if (need_hle_lock) { + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + event.writable->Signal(); + } else { + event.writable->Signal(); + } +} + +Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {} + +Backend::~Backend() = default; + +NullBackend::NullBackend(DirectoryGetter getter) : Backend(std::move(getter)) {} + +NullBackend::~NullBackend() = default; + +bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, + title.build_id); + + progress.FinishDownload(RESULT_SUCCESS); + return true; +} + +bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, + title.build_id, name); + + progress.FinishDownload(RESULT_SUCCESS); + return true; +} + +bool NullBackend::Clear(u64 title_id) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}"); + + return true; +} + +void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id, + Common::HexToString(passphrase)); +} + +std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, + title.build_id); + return std::nullopt; +} + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h new file mode 100644 index 000000000..51dbd3316 --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.h @@ -0,0 +1,150 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include <string> +#include <string_view> + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/result.h" + +namespace Service::BCAT { + +struct DeliveryCacheProgressImpl; + +using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>; +using Passphrase = std::array<u8, 0x20>; + +struct TitleIDVersion { + u64 title_id; + u64 build_id; +}; + +using DirectoryName = std::array<char, 0x20>; +using FileName = std::array<char, 0x20>; + +struct DeliveryCacheProgressImpl { + enum class Status : s32 { + None = 0x0, + Queued = 0x1, + Connecting = 0x2, + ProcessingDataList = 0x3, + Downloading = 0x4, + Committing = 0x5, + Done = 0x9, + }; + + Status status; + ResultCode result = RESULT_SUCCESS; + DirectoryName current_directory; + FileName current_file; + s64 current_downloaded_bytes; ///< Bytes downloaded on current file. + s64 current_total_bytes; ///< Bytes total on current file. + s64 total_downloaded_bytes; ///< Bytes downloaded on overall download. + s64 total_bytes; ///< Bytes total on overall download. + INSERT_PADDING_BYTES( + 0x198); ///< Appears to be unused in official code, possibly reserved for future use. +}; +static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200, + "DeliveryCacheProgressImpl has incorrect size."); + +// A class to manage the signalling to the game about BCAT download progress. +// Some of this class is implemented in module.cpp to avoid exposing the implementation structure. +class ProgressServiceBackend { + friend class IBcatService; + +public: + // Clients should call this with true if any of the functions are going to be called from a + // non-HLE thread and this class need to lock the hle mutex. (default is false) + void SetNeedHLELock(bool need); + + // Sets the number of bytes total in the entire download. + void SetTotalSize(u64 size); + + // Notifies the application that the backend has started connecting to the server. + void StartConnecting(); + // Notifies the application that the backend has begun accumulating and processing metadata. + void StartProcessingDataList(); + + // Notifies the application that a file is starting to be downloaded. + void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size); + // Updates the progress of the current file to the size passed. + void UpdateFileProgress(u64 downloaded); + // Notifies the application that the current file has completed download. + void FinishDownloadingFile(); + + // Notifies the application that all files in this directory have completed and are being + // finalized. + void CommitDirectory(std::string_view dir_name); + + // Notifies the application that the operation completed with result code result. + void FinishDownload(ResultCode result); + +private: + explicit ProgressServiceBackend(std::string_view event_name); + + Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent() const; + DeliveryCacheProgressImpl& GetImpl(); + + void SignalUpdate() const; + + DeliveryCacheProgressImpl impl{}; + Kernel::EventPair event; + bool need_hle_lock = false; +}; + +// A class representing an abstract backend for BCAT functionality. +class Backend { +public: + explicit Backend(DirectoryGetter getter); + virtual ~Backend(); + + // Called when the backend is needed to synchronize the data for the game with title ID and + // version in title. A ProgressServiceBackend object is provided to alert the application of + // status. + virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0; + // Very similar to Synchronize, but only for the directory provided. Backends should not alter + // the data for any other directories. + virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) = 0; + + // Removes all cached data associated with title id provided. + virtual bool Clear(u64 title_id) = 0; + + // Sets the BCAT Passphrase to be used with the associated title ID. + virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0; + + // Gets the launch parameter used by AM associated with the title ID and version provided. + virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0; + +protected: + DirectoryGetter dir_getter; +}; + +// A backend of BCAT that provides no operation. +class NullBackend : public Backend { +public: + explicit NullBackend(DirectoryGetter getter); + ~NullBackend() override; + + bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; + bool SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) override; + + bool Clear(u64 title_id) override; + + void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; + + std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; +}; + +std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter); + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp new file mode 100644 index 000000000..64022982b --- /dev/null +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -0,0 +1,504 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/ostream.h> +#include <httplib.h> +#include <json.hpp> +#include <mbedtls/sha256.h> +#include "common/hex_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_libzip.h" +#include "core/file_sys/vfs_vector.h" +#include "core/frontend/applets/error.h" +#include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/bcat/backend/boxcat.h" +#include "core/settings.h" + +namespace { + +// Prevents conflicts with windows macro called CreateFile +FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) { + return dir->CreateFile(name); +} + +// Prevents conflicts with windows macro called DeleteFile +bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) { + return dir->DeleteFile(name); +} + +} // Anonymous namespace + +namespace Service::BCAT { + +constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1}; + +constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; + +// Formatted using fmt with arg[0] = hex title id +constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat"; +constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam"; + +constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events"; + +constexpr char BOXCAT_API_VERSION[] = "1"; +constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu"; + +// HTTP status codes for Boxcat +enum class ResponseStatus { + Ok = 200, ///< Operation completed successfully. + BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server. + NoUpdate = 304, ///< The digest provided would match the new data, no need to update. + NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation. + NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format + ///< issues or whatnot) and has no data. +}; + +enum class DownloadResult { + Success = 0, + NoResponse, + GeneralWebError, + NoMatchTitleId, + NoMatchBuildId, + InvalidContentType, + GeneralFSError, + BadClientVersion, +}; + +constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{ + "Success", + "There was no response from the server.", + "There was a general web error code returned from the server.", + "The title ID of the current game doesn't have a boxcat implementation. If you believe an " + "implementation should be added, contact yuzu support.", + "The build ID of the current version of the game is marked as incompatible with the current " + "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.", + "The content type of the web response was invalid.", + "There was a general filesystem error while saving the zip file.", + "The server is either too new or too old to serve the request. Try using the latest version of " + "an official release of yuzu.", +}; + +std::ostream& operator<<(std::ostream& os, DownloadResult result) { + return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result)); +} + +constexpr u32 PORT = 443; +constexpr u32 TIMEOUT_SECONDS = 30; +[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB + +namespace { + +std::string GetBINFilePath(u64 title_id) { + return fmt::format("{}bcat/{:016X}/launchparam.bin", + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); +} + +std::string GetZIPFilePath(u64 title_id) { + return fmt::format("{}bcat/{:016X}/data.zip", + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); +} + +// If the error is something the user should know about (build ID mismatch, bad client version), +// display an error. +void HandleDownloadDisplayResult(DownloadResult res) { + if (res == DownloadResult::Success || res == DownloadResult::NoResponse || + res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError || + res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) { + return; + } + + const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()}; + frontend.error->ShowCustomErrorText( + ResultCode(-1), "There was an error while attempting to use Boxcat.", + DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {}); +} + +bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest, + std::string_view dir_name, ProgressServiceBackend& progress, + std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + if (!dest->Resize(src->GetSize())) + return false; + + progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize()); + + std::vector<u8> temp(std::min(block_size, src->GetSize())); + for (std::size_t i = 0; i < src->GetSize(); i += block_size) { + const auto read = std::min(block_size, src->GetSize() - i); + + if (src->Read(temp.data(), read, i) != read) { + return false; + } + + if (dest->Write(temp.data(), read, i) != read) { + return false; + } + + progress.UpdateFileProgress(i); + } + + progress.FinishDownloadingFile(); + + return true; +} + +bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest, + ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& file : src->GetFiles()) { + const auto out_file = VfsCreateFileWrap(dest, file->GetName()); + if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) { + return false; + } + } + progress.CommitDirectory(src->GetName()); + + return true; +} + +bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest, + ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& dir : src->GetSubdirectories()) { + const auto out = dest->CreateSubdirectory(dir->GetName()); + if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) { + return false; + } + } + + return true; +} + +} // Anonymous namespace + +class Boxcat::Client { +public: + Client(std::string path, u64 title_id, u64 build_id) + : path(std::move(path)), title_id(title_id), build_id(build_id) {} + + DownloadResult DownloadDataZip() { + return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS, + "application/zip"); + } + + DownloadResult DownloadLaunchParam() { + return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id), + TIMEOUT_SECONDS / 3, "application/octet-stream"); + } + +private: + DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, + const std::string& content_type_name) { + if (client == nullptr) { + client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds); + } + + httplib::Headers headers{ + {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, + {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, + {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)}, + }; + + if (FileUtil::Exists(path)) { + FileUtil::IOFile file{path, "rb"}; + if (file.IsOpen()) { + std::vector<u8> bytes(file.GetSize()); + file.ReadBytes(bytes.data(), bytes.size()); + const auto digest = DigestFile(bytes); + headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); + } + } + + const auto response = client->Get(resolved_path.c_str(), headers); + if (response == nullptr) + return DownloadResult::NoResponse; + + if (response->status == static_cast<int>(ResponseStatus::NoUpdate)) + return DownloadResult::Success; + if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) + return DownloadResult::BadClientVersion; + if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId)) + return DownloadResult::NoMatchTitleId; + if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) + return DownloadResult::NoMatchBuildId; + if (response->status != static_cast<int>(ResponseStatus::Ok)) + return DownloadResult::GeneralWebError; + + const auto content_type = response->headers.find("content-type"); + if (content_type == response->headers.end() || + content_type->second.find(content_type_name) == std::string::npos) { + return DownloadResult::InvalidContentType; + } + + FileUtil::CreateFullPath(path); + FileUtil::IOFile file{path, "wb"}; + if (!file.IsOpen()) + return DownloadResult::GeneralFSError; + if (!file.Resize(response->body.size())) + return DownloadResult::GeneralFSError; + if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size()) + return DownloadResult::GeneralFSError; + + return DownloadResult::Success; + } + + using Digest = std::array<u8, 0x20>; + static Digest DigestFile(std::vector<u8> bytes) { + Digest out{}; + mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0); + return out; + } + + std::unique_ptr<httplib::Client> client; + std::string path; + u64 title_id; + u64 build_id; +}; + +Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {} + +Boxcat::~Boxcat() = default; + +void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, + ProgressServiceBackend& progress, + std::optional<std::string> dir_name = {}) { + progress.SetNeedHLELock(true); + + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); + const auto dir = dir_getter(title.title_id); + if (dir) + progress.SetTotalSize(dir->GetSize()); + progress.FinishDownload(RESULT_SUCCESS); + return; + } + + const auto zip_path{GetZIPFilePath(title.title_id)}; + Boxcat::Client client{zip_path, title.title_id, title.build_id}; + + progress.StartConnecting(); + + const auto res = client.DownloadDataZip(); + if (res != DownloadResult::Success) { + LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); + + if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { + FileUtil::Delete(zip_path); + } + + HandleDownloadDisplayResult(res); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + progress.StartProcessingDataList(); + + FileUtil::IOFile zip{zip_path, "rb"}; + const auto size = zip.GetSize(); + std::vector<u8> bytes(size); + if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { + LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes)); + if (extracted == nullptr) { + LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + if (dir_name == std::nullopt) { + progress.SetTotalSize(extracted->GetSize()); + + const auto target_dir = dir_getter(title.title_id); + if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) { + LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + } else { + const auto target_dir = dir_getter(title.title_id); + if (target_dir == nullptr) { + LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + const auto target_sub = target_dir->GetSubdirectory(*dir_name); + const auto source_sub = extracted->GetSubdirectory(*dir_name); + + progress.SetTotalSize(source_sub->GetSize()); + + std::vector<std::string> filenames; + { + const auto files = target_sub->GetFiles(); + std::transform(files.begin(), files.end(), std::back_inserter(filenames), + [](const auto& vfile) { return vfile->GetName(); }); + } + + for (const auto& filename : filenames) { + VfsDeleteFileWrap(target_sub, filename); + } + + if (target_sub == nullptr || source_sub == nullptr || + !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) { + LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + } + + progress.FinishDownload(RESULT_SUCCESS); +} + +bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { + is_syncing.exchange(true); + std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); }) + .detach(); + return true; +} + +bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) { + is_syncing.exchange(true); + std::thread( + [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); }) + .detach(); + return true; +} + +bool Boxcat::Clear(u64 title_id) { + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear."); + return true; + } + + const auto dir = dir_getter(title_id); + + std::vector<std::string> dirnames; + + for (const auto& subdir : dir->GetSubdirectories()) + dirnames.push_back(subdir->GetName()); + + for (const auto& subdir : dirnames) { + if (!dir->DeleteSubdirectoryRecursive(subdir)) + return false; + } + + return true; +} + +void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, + Common::HexToString(passphrase)); +} + +std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { + const auto path{GetBINFilePath(title.title_id)}; + + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); + } else { + Boxcat::Client client{path, title.title_id, title.build_id}; + + const auto res = client.DownloadLaunchParam(); + if (res != DownloadResult::Success) { + LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); + + if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { + FileUtil::Delete(path); + } + + HandleDownloadDisplayResult(res); + return std::nullopt; + } + } + + FileUtil::IOFile bin{path, "rb"}; + const auto size = bin.GetSize(); + std::vector<u8> bytes(size); + if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { + LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", + path); + return std::nullopt; + } + + return bytes; +} + +Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, + std::map<std::string, EventStatus>& games) { + httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), + static_cast<int>(TIMEOUT_SECONDS)}; + + httplib::Headers headers{ + {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, + {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, + }; + + const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); + if (response == nullptr) + return StatusResult::Offline; + + if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) + return StatusResult::BadClientVersion; + + try { + nlohmann::json json = nlohmann::json::parse(response->body); + + if (!json["online"].get<bool>()) + return StatusResult::Offline; + + if (json["global"].is_null()) + global = std::nullopt; + else + global = json["global"].get<std::string>(); + + if (json["games"].is_array()) { + for (const auto object : json["games"]) { + if (object.is_object() && object.find("name") != object.end()) { + EventStatus detail{}; + if (object["header"].is_string()) { + detail.header = object["header"].get<std::string>(); + } else { + detail.header = std::nullopt; + } + + if (object["footer"].is_string()) { + detail.footer = object["footer"].get<std::string>(); + } else { + detail.footer = std::nullopt; + } + + if (object["events"].is_array()) { + for (const auto& event : object["events"]) { + if (!event.is_string()) + continue; + detail.events.push_back(event.get<std::string>()); + } + } + + games.insert_or_assign(object["name"], std::move(detail)); + } + } + } + + return StatusResult::Success; + } catch (const nlohmann::json::parse_error& error) { + LOG_ERROR(Service_BCAT, "{}", error.what()); + return StatusResult::ParseError; + } +} + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h new file mode 100644 index 000000000..601151189 --- /dev/null +++ b/src/core/hle/service/bcat/backend/boxcat.h @@ -0,0 +1,58 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <map> +#include <optional> +#include "core/hle/service/bcat/backend/backend.h" + +namespace Service::BCAT { + +struct EventStatus { + std::optional<std::string> header; + std::optional<std::string> footer; + std::vector<std::string> events; +}; + +/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and +/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team. +class Boxcat final : public Backend { + friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, + ProgressServiceBackend& progress, + std::optional<std::string> dir_name); + +public: + explicit Boxcat(DirectoryGetter getter); + ~Boxcat() override; + + bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; + bool SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) override; + + bool Clear(u64 title_id) override; + + void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; + + std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; + + enum class StatusResult { + Success, + Offline, + ParseError, + BadClientVersion, + }; + + static StatusResult GetStatus(std::optional<std::string>& global, + std::map<std::string, EventStatus>& games); + +private: + std::atomic_bool is_syncing{false}; + + class Client; + std::unique_ptr<Client> client; +}; + +} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp index 179aa4949..8bb2528c9 100644 --- a/src/core/hle/service/bcat/bcat.cpp +++ b/src/core/hle/service/bcat/bcat.cpp @@ -6,11 +6,16 @@ namespace Service::BCAT { -BCAT::BCAT(std::shared_ptr<Module> module, const char* name) - : Module::Interface(std::move(module), name) { +BCAT::BCAT(Core::System& system, std::shared_ptr<Module> module, + FileSystem::FileSystemController& fsc, const char* name) + : Interface(system, std::move(module), fsc, name) { + // clang-format off static const FunctionInfo functions[] = { {0, &BCAT::CreateBcatService, "CreateBcatService"}, + {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"}, + {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"}, }; + // clang-format on RegisterHandlers(functions); } diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index 802bd689a..6354465fc 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -6,11 +6,16 @@ #include "core/hle/service/bcat/module.h" +namespace Core { +class System; +} + namespace Service::BCAT { class BCAT final : public Module::Interface { public: - explicit BCAT(std::shared_ptr<Module> module, const char* name); + explicit BCAT(Core::System& system, std::shared_ptr<Module> module, + FileSystem::FileSystemController& fsc, const char* name); ~BCAT() override; }; diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index b7bd738fc..4e4aa758b 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -2,34 +2,257 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cctype> +#include <mbedtls/md5.h> +#include "backend/boxcat.h" +#include "common/hex_util.h" #include "common/logging/log.h" +#include "common/string_util.h" +#include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/bcat/bcat.h" #include "core/hle/service/bcat/module.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/settings.h" namespace Service::BCAT { +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1}; +constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2}; +constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6}; +constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7}; + +// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files +// and if any of them have a non-zero result it just forwards that result. This is the FS error code +// for permission denied, which is the closest approximation of this scenario. +constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400}; + +using BCATDigest = std::array<u8, 0x10>; + +namespace { + +u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) { + u64 out{}; + std::memcpy(&out, id.data(), sizeof(u64)); + return out; +} + +// The digest is only used to determine if a file is unique compared to others of the same name. +// Since the algorithm isn't ever checked in game, MD5 is safe. +BCATDigest DigestFile(const FileSys::VirtualFile& file) { + BCATDigest out{}; + const auto bytes = file->ReadAllBytes(); + mbedtls_md5(bytes.data(), bytes.size(), out.data()); + return out; +} + +// For a name to be valid it must be non-empty, must have a null terminating character as the final +// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if +// file. +bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name, + char match_char) { + const auto null_chars = std::count(name.begin(), name.end(), 0); + const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) { + return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0'; + }); + if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') { + LOG_ERROR(Service_BCAT, "Name passed was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return false; + } + + return true; +} + +bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) { + return VerifyNameValidInternal(ctx, name, '-'); +} + +bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) { + return VerifyNameValidInternal(ctx, name, '.'); +} + +} // Anonymous namespace + +struct DeliveryCacheDirectoryEntry { + FileName name; + u64 size; + BCATDigest digest; +}; + +class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> { +public: + IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event, + const DeliveryCacheProgressImpl& impl) + : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, + {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + } + + void GetImpl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Kernel::SharedPtr<Kernel::ReadableEvent> event; + const DeliveryCacheProgressImpl& impl; +}; + class IBcatService final : public ServiceFramework<IBcatService> { public: - IBcatService() : ServiceFramework("IBcatService") { + explicit IBcatService(Core::System& system_, Backend& backend_) + : ServiceFramework("IBcatService"), system{system_}, backend{backend_} { + // clang-format off static const FunctionInfo functions[] = { - {10100, nullptr, "RequestSyncDeliveryCache"}, - {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"}, + {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"}, + {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"}, {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, - {30100, nullptr, "SetPassphrase"}, + {30100, &IBcatService::SetPassphrase, "SetPassphrase"}, {30200, nullptr, "RegisterBackgroundDeliveryTask"}, {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, {30202, nullptr, "BlockDeliveryTask"}, {30203, nullptr, "UnblockDeliveryTask"}, {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, {90200, nullptr, "GetDeliveryList"}, - {90201, nullptr, "ClearDeliveryCacheStorage"}, + {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"}, {90300, nullptr, "GetPushNotificationLog"}, }; + // clang-format on RegisterHandlers(functions); } + +private: + enum class SyncType { + Normal, + Directory, + Count, + }; + + std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) { + auto& backend{progress.at(static_cast<std::size_t>(type))}; + return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(), + backend.GetImpl()); + } + + void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + backend.Synchronize({system.CurrentProcess()->GetTitleID(), + GetCurrentBuildID(system.GetCurrentProcessBuildID())}, + progress.at(static_cast<std::size_t>(SyncType::Normal))); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(CreateProgressService(SyncType::Normal)); + } + + void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw<DirectoryName>(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + backend.SynchronizeDirectory({system.CurrentProcess()->GetTitleID(), + GetCurrentBuildID(system.GetCurrentProcessBuildID())}, + name, + progress.at(static_cast<std::size_t>(SyncType::Directory))); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(CreateProgressService(SyncType::Directory)); + } + + void SetPassphrase(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + const auto passphrase_raw = ctx.ReadBuffer(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, + Common::HexToString(passphrase_raw)); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + } + + if (passphrase_raw.size() > 0x40) { + LOG_ERROR(Service_BCAT, "Passphrase too large!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Passphrase passphrase{}; + std::memcpy(passphrase.data(), passphrase_raw.data(), + std::min(passphrase.size(), passphrase_raw.size())); + + backend.SetPassphrase(title_id, passphrase); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + if (!backend.Clear(title_id)) { + LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_CLEAR_CACHE); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Core::System& system; + Backend& backend; + + std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{ + ProgressServiceBackend{"Normal"}, + ProgressServiceBackend{"Directory"}, + }; }; void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { @@ -37,20 +260,332 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IBcatService>(); + rb.PushIpcInterface<IBcatService>(system, *backend); +} + +class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> { +public: + IDeliveryCacheFileService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheFileService::Open, "Open"}, + {1, &IDeliveryCacheFileService::Read, "Read"}, + {2, &IDeliveryCacheFileService::GetSize, "GetSize"}, + {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto dir_name_raw = rp.PopRaw<DirectoryName>(); + const auto file_name_raw = rp.PopRaw<FileName>(); + + const auto dir_name = + Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size()); + const auto file_name = + Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name); + + if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw)) + return; + + if (current_file != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + const auto dir = root->GetSubdirectory(dir_name); + + if (dir == nullptr) { + LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + current_file = dir->GetFile(file_name); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto offset{rp.PopRaw<u64>()}; + + auto size = ctx.GetWriteBufferSize(); + + LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + size = std::min<u64>(current_file->GetSize() - offset, size); + const auto buffer = current_file->ReadBytes(size, offset); + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(buffer.size()); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(current_file->GetSize()); + } + + void GetDigest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(DigestFile(current_file)); + } + + FileSys::VirtualDir root; + FileSys::VirtualFile current_file; +}; + +class IDeliveryCacheDirectoryService final + : public ServiceFramework<IDeliveryCacheDirectoryService> { +public: + IDeliveryCacheDirectoryService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheDirectoryService::Open, "Open"}, + {1, &IDeliveryCacheDirectoryService::Read, "Read"}, + {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw<DirectoryName>(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + if (!VerifyNameValidDir(ctx, name_raw)) + return; + + if (current_dir != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + current_dir = root->GetSubdirectory(name); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Read(Kernel::HLERequestContext& ctx) { + auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry); + + LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + write_size = std::min<u64>(write_size, files.size()); + std::vector<DeliveryCacheDirectoryEntry> entries(write_size); + std::transform( + files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) { + FileName name{}; + std::memcpy(name.data(), file->GetName().data(), + std::min(file->GetName().size(), name.size())); + return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)}; + }); + + ctx.WriteBuffer(entries); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry))); + } + + void GetCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(files.size())); + } + + FileSys::VirtualDir root; + FileSys::VirtualDir current_dir; +}; + +class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> { +public: + IDeliveryCacheStorageService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, + {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"}, + {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"}, + }; + // clang-format on + + RegisterHandlers(functions); + + for (const auto& subdir : root->GetSubdirectories()) { + DirectoryName name{}; + std::memcpy(name.data(), subdir->GetName().data(), + std::min(sizeof(DirectoryName) - 1, subdir->GetName().size())); + entries.push_back(name); + } + } + +private: + void CreateFileService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheFileService>(root); + } + + void CreateDirectoryService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root); + } + + void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { + auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName); + + LOG_DEBUG(Service_BCAT, "called, size={:016X}", size); + + size = std::min<u64>(size, entries.size() - next_read_index); + ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName)); + next_read_index += size; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(size)); + } + + FileSys::VirtualDir root; + std::vector<DirectoryName> entries; + u64 next_read_index = 0; +}; + +void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheStorageService>( + fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID())); +} + +void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id)); +} + +std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) { + const auto backend = Settings::values.bcat_backend; + +#ifdef YUZU_ENABLE_BOXCAT + if (backend == "boxcat") + return std::make_unique<Boxcat>(std::move(getter)); +#endif + + return std::make_unique<NullBackend>(std::move(getter)); } -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, + FileSystem::FileSystemController& fsc_, const char* name) + : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)}, + backend{CreateBackendFromSettings([&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })}, + system{system_} {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager); + std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:a") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:m") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:u") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(system, module, system.GetFileSystemController(), "bcat:s") + ->InstallAsService(system.ServiceManager()); } } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index f0d63cab0..e4ba23ba0 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -6,23 +6,46 @@ #include "core/hle/service/service.h" -namespace Service::BCAT { +namespace Core { +class System; +} + +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace BCAT { + +class Backend; class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(Core::System& system_, std::shared_ptr<Module> module_, + FileSystem::FileSystemController& fsc_, const char* name); ~Interface() override; void CreateBcatService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx); protected: + FileSystem::FileSystemController& fsc; + std::shared_ptr<Module> module; + std::unique_ptr<Backend> backend; + + private: + Core::System& system; }; }; /// Registers all BCAT services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); + +} // namespace BCAT -} // namespace Service::BCAT +} // namespace Service diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index b2ebf6240..2546d7595 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -66,7 +66,7 @@ enum class FatalType : u32 { static void GenerateErrorReport(Core::System& system, ResultCode error_code, const FatalInfo& info) { - const auto title_id = Core::CurrentProcess()->GetTitleID(); + const auto title_id = system.CurrentProcess()->GetTitleID(); std::string crash_report = fmt::format( "Yuzu {}-{} crash report\n" "Title ID: {:016x}\n" diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 14cd0e322..11e5c56b7 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -241,7 +241,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( return FileSys::ERROR_PATH_NOT_FOUND; } -FileSystemController::FileSystemController() = default; +FileSystemController::FileSystemController(Core::System& system_) : system{system_} {} FileSystemController::~FileSystemController() = default; @@ -290,7 +290,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() return ResultCode(-1); } - return romfs_factory->OpenCurrentProcess(); + return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID()); } ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( @@ -447,10 +447,10 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy FileSys::SaveDataSize new_size{SUFFICIENT_SAVE_DATA_SIZE, SUFFICIENT_SAVE_DATA_SIZE}; FileSys::NACP nacp; - const auto res = Core::System::GetInstance().GetAppLoader().ReadControlData(nacp); + const auto res = system.GetAppLoader().ReadControlData(nacp); if (res != Loader::ResultStatus::Success) { - FileSys::PatchManager pm{Core::CurrentProcess()->GetTitleID()}; + FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; auto [nacp_unique, discard] = pm.GetControlMetadata(); if (nacp_unique != nullptr) { @@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) return bis_factory->GetModificationDumpRoot(title_id); } +FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const { + LOG_TRACE(Service_FS, "Opening BCAT root for tid={:016X}", title_id); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetBCATDirectory(title_id); +} + void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { if (overwrite) { bis_factory = nullptr; @@ -693,10 +702,10 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove if (bis_factory == nullptr) { bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); - Core::System::GetInstance().RegisterContentProvider( - FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents()); - Core::System::GetInstance().RegisterContentProvider( - FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents()); + system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, + bis_factory->GetSystemNANDContents()); + system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, + bis_factory->GetUserNANDContents()); } if (save_data_factory == nullptr) { @@ -705,8 +714,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove if (sdmc_factory == nullptr) { sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); - Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, - sdmc_factory->GetSDMCContents()); + system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, + sdmc_factory->GetSDMCContents()); } } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 3e0c03ec0..1b0a6a949 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -10,6 +10,10 @@ #include "core/file_sys/vfs.h" #include "core/hle/result.h" +namespace Core { +class System; +} + namespace FileSys { class BISFactory; class RegisteredCache; @@ -52,7 +56,7 @@ enum class ImageDirectoryId : u32 { class FileSystemController { public: - FileSystemController(); + explicit FileSystemController(Core::System& system_); ~FileSystemController(); ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); @@ -110,6 +114,8 @@ public: FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; + FileSys::VirtualDir GetBCATDirectory(u64 title_id) const; + // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function // above is called. void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); @@ -123,6 +129,8 @@ private: std::unique_ptr<FileSys::XCI> gamecard; std::unique_ptr<FileSys::RegisteredCache> gamecard_registered; std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder; + + Core::System& system; }; void InstallInterfaces(Core::System& system); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index eb982ad49..cbd5466c1 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -803,7 +803,7 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>(); - auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); + [[maybe_unused]] auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); u128 uid = rp.PopRaw<u128>(); LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 42b4ee861..75dd9043b 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -237,7 +237,6 @@ private: }; Common::UUID uuid; - bool is_event_created = false; Kernel::EventPair notification_event; std::queue<SizedNotificationInfo> notifications; States states{}; diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 8e8263f5b..1f2131ec8 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -11,11 +11,10 @@ namespace Service::HID { constexpr s32 HID_JOYSTICK_MAX = 0x7fff; -constexpr s32 HID_JOYSTICK_MIN = -0x7fff; +[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; -Controller_DebugPad::Controller_DebugPad(Core::System& system) - : ControllerBase(system), system(system) {} +Controller_DebugPad::Controller_DebugPad(Core::System& system) : ControllerBase(system) {} Controller_DebugPad::~Controller_DebugPad() = default; void Controller_DebugPad::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 6c4de817e..555b29d76 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -89,6 +89,5 @@ private: buttons; std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> analogs; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 80da0a0d3..6e990dd00 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -10,8 +10,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00; -Controller_Gesture::Controller_Gesture(Core::System& system) - : ControllerBase(system), system(system) {} +Controller_Gesture::Controller_Gesture(Core::System& system) : ControllerBase(system) {} Controller_Gesture::~Controller_Gesture() = default; void Controller_Gesture::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index 396897527..f650b8338 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -59,6 +59,5 @@ private: std::array<GestureState, 17> gesture_states; }; SharedMemory shared_memory{}; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index e587b2e15..358cb9329 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -12,8 +12,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; constexpr u8 KEYS_PER_BYTE = 8; -Controller_Keyboard::Controller_Keyboard(Core::System& system) - : ControllerBase(system), system(system) {} +Controller_Keyboard::Controller_Keyboard(Core::System& system) : ControllerBase(system) {} Controller_Keyboard::~Controller_Keyboard() = default; void Controller_Keyboard::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index ef586f7eb..f3eef5936 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -53,6 +53,5 @@ private: keyboard_keys; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods> keyboard_mods; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 88f2ca4c1..93d88ea50 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -11,7 +11,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; -Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(system) {} +Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system) {} Controller_Mouse::~Controller_Mouse() = default; void Controller_Mouse::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index df2da6ae3..357ab7107 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -53,6 +53,5 @@ private: std::unique_ptr<Input::MouseDevice> mouse_device; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons> mouse_button_devices; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 44b668fbf..a2b25a796 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -20,7 +20,7 @@ namespace Service::HID { constexpr s32 HID_JOYSTICK_MAX = 0x7fff; -constexpr s32 HID_JOYSTICK_MIN = -0x7fff; +[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; constexpr u32 MAX_NPAD_ID = 7; @@ -105,6 +105,8 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.joy_styles.raw = 0; // Zero out controller.device_type.raw = 0; switch (controller_type) { + case NPadControllerType::None: + UNREACHABLE(); case NPadControllerType::Handheld: controller.joy_styles.handheld.Assign(1); controller.device_type.handheld.Assign(1); @@ -165,13 +167,14 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; controller.battery_level[2] = BATTERY_FULL; + styleset_changed_events[controller_idx].writable->Signal(); } void Controller_NPad::OnInit() { auto& kernel = system.Kernel(); for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( - kernel, Kernel::ResetType::Automatic, fmt::format("npad:NpadStyleSetChanged_{}", i)); + kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i)); } if (!IsControllerActivated()) { @@ -238,7 +241,7 @@ void Controller_NPad::OnRelease() {} void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { const auto controller_idx = NPadIdToIndex(npad_id); - const auto controller_type = connected_controllers[controller_idx].type; + [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type; if (!connected_controllers[controller_idx].is_connected) { return; } @@ -345,6 +348,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* libnx_entry.connection_status.raw = 0; switch (controller_type) { + case NPadControllerType::None: + UNREACHABLE(); case NPadControllerType::Handheld: handheld_entry.connection_status.raw = 0; handheld_entry.connection_status.IsWired.Assign(1); @@ -433,7 +438,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); std::memcpy(supported_npad_id_types.data(), data, length); - bool had_controller_update = false; for (std::size_t i = 0; i < connected_controllers.size(); i++) { auto& controller = connected_controllers[i]; if (!controller.is_connected) { @@ -452,10 +456,6 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { controller.type = requested_controller; InitNewlyAddedControler(i); } - had_controller_update = true; - } - if (had_controller_update) { - styleset_changed_events[i].writable->Signal(); } } } @@ -481,7 +481,6 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) const std::size_t npad_index = NPadIdToIndex(npad_id); ASSERT(npad_index < shared_memory_entries.size()); if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { - styleset_changed_events[npad_index].writable->Signal(); shared_memory_entries[npad_index].pad_assignment = assignment_mode; } } @@ -507,7 +506,6 @@ Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEven // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should // be signalled at least once, and signaled after a new controller is connected? const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; - styleset_event.writable->Signal(); return styleset_event.readable; } diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 9b829341e..9e527d176 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -9,8 +9,7 @@ namespace Service::HID { -Controller_Stubbed::Controller_Stubbed(Core::System& system) - : ControllerBase(system), system(system) {} +Controller_Stubbed::Controller_Stubbed(Core::System& system) : ControllerBase(system) {} Controller_Stubbed::~Controller_Stubbed() = default; void Controller_Stubbed::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h index 37d7d8538..4fa83ac85 100644 --- a/src/core/hle/service/hid/controllers/stubbed.h +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -30,6 +30,5 @@ public: private: bool smart_update{}; std::size_t common_offset{}; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 25912fd69..1c6e55566 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -13,8 +13,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; -Controller_Touchscreen::Controller_Touchscreen(Core::System& system) - : ControllerBase(system), system(system) {} +Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {} Controller_Touchscreen::~Controller_Touchscreen() = default; void Controller_Touchscreen::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 3429c84db..a1d97269e 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -69,6 +69,5 @@ private: TouchScreenSharedMemory shared_memory{}; std::unique_ptr<Input::TouchDevice> touch_device; s64_le last_touch{}; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp index 1bce044b4..27511b27b 100644 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -10,7 +10,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; -Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(system) {} +Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system) {} Controller_XPad::~Controller_XPad() = default; void Controller_XPad::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h index c445ebec0..ad229787c 100644 --- a/src/core/hle/service/hid/controllers/xpad.h +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -56,6 +56,5 @@ private: }; static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; - Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 8d76ba746..ba1da4181 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -38,8 +38,10 @@ namespace Service::HID { // Updating period for each HID device. // TODO(ogniK): Find actual polling rate of hid constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66); -constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); -constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); +[[maybe_unused]] constexpr s64 accelerometer_update_ticks = + static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); +[[maybe_unused]] constexpr s64 gyroscope_update_ticks = + static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; IAppletResource::IAppletResource(Core::System& system) @@ -193,7 +195,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, {103, &Hid::ActivateNpad, "ActivateNpad"}, - {104, nullptr, "DeactivateNpad"}, + {104, &Hid::DeactivateNpad, "DeactivateNpad"}, {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, {107, &Hid::DisconnectNpad, "DisconnectNpad"}, {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, @@ -468,6 +470,17 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) { applet_resource->ActivateController(HidController::NPad); } +void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + applet_resource->DeactivateController(HidController::NPad); +} + void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto npad_id{rp.Pop<u32>()}; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 35b663679..01852e019 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -99,6 +99,7 @@ private: void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx); void ActivateNpad(Kernel::HLERequestContext& ctx); + void DeactivateNpad(Kernel::HLERequestContext& ctx); void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx); void DisconnectNpad(Kernel::HLERequestContext& ctx); void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 3164ca26e..499376bfc 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -163,7 +163,7 @@ public: return; } - if (Core::CurrentProcess()->GetTitleID() != header.title_id) { + if (system.CurrentProcess()->GetTitleID() != header.title_id) { LOG_ERROR(Service_LDR, "Attempting to load NRR with title ID other than current process. (actual " "{:016X})!", @@ -327,7 +327,7 @@ public: } // Load NRO as new executable module - auto* process = Core::CurrentProcess(); + auto* process = system.CurrentProcess(); auto& vm_manager = process->VMManager(); auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); @@ -411,7 +411,7 @@ public: return; } - auto& vm_manager = Core::CurrentProcess()->VMManager(); + auto& vm_manager = system.CurrentProcess()->VMManager(); const auto& nro_info = iter->second; // Unmap the mirrored memory diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 2a61593e2..435f2d286 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -6,8 +6,10 @@ #include <string> #include "common/logging/log.h" +#include "common/scope_exit.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/lm/lm.h" +#include "core/hle/service/lm/manager.h" #include "core/hle/service/service.h" #include "core/memory.h" @@ -15,65 +17,16 @@ namespace Service::LM { class ILogger final : public ServiceFramework<ILogger> { public: - ILogger() : ServiceFramework("ILogger") { + ILogger(Manager& manager) : ServiceFramework("ILogger"), manager(manager) { static const FunctionInfo functions[] = { - {0x00000000, &ILogger::Initialize, "Initialize"}, - {0x00000001, &ILogger::SetDestination, "SetDestination"}, + {0, &ILogger::Log, "Log"}, + {1, &ILogger::SetDestination, "SetDestination"}, }; RegisterHandlers(functions); } private: - struct MessageHeader { - enum Flags : u32_le { - IsHead = 1, - IsTail = 2, - }; - enum Severity : u32_le { - Trace, - Info, - Warning, - Error, - Critical, - }; - - u64_le pid; - u64_le threadContext; - union { - BitField<0, 16, Flags> flags; - BitField<16, 8, Severity> severity; - BitField<24, 8, u32> verbosity; - }; - u32_le payload_size; - - bool IsHeadLog() const { - return flags & Flags::IsHead; - } - bool IsTailLog() const { - return flags & Flags::IsTail; - } - }; - static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size"); - - /// Log field type - enum class Field : u8 { - Skip = 1, - Message = 2, - Line = 3, - Filename = 4, - Function = 5, - Module = 6, - Thread = 7, - }; - - /** - * ILogger::Initialize service function - * Inputs: - * 0: 0x00000000 - * Outputs: - * 0: ResultCode - */ - void Initialize(Kernel::HLERequestContext& ctx) { + void Log(Kernel::HLERequestContext& ctx) { // This function only succeeds - Get that out of the way IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -85,140 +38,70 @@ private: Memory::ReadBlock(addr, &header, sizeof(MessageHeader)); addr += sizeof(MessageHeader); - if (header.IsHeadLog()) { - log_stream.str(""); - log_stream.clear(); - } - - // Parse out log metadata - u32 line{}; - std::string module; - std::string message; - std::string filename; - std::string function; - std::string thread; + FieldMap fields; while (addr < end_addr) { - const Field field{static_cast<Field>(Memory::Read8(addr++))}; - const std::size_t length{Memory::Read8(addr++)}; + const auto field = static_cast<Field>(Memory::Read8(addr++)); + const auto length = Memory::Read8(addr++); if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) { ++addr; } - switch (field) { - case Field::Skip: - break; - case Field::Message: - message = Memory::ReadCString(addr, length); - break; - case Field::Line: - line = Memory::Read32(addr); - break; - case Field::Filename: - filename = Memory::ReadCString(addr, length); - break; - case Field::Function: - function = Memory::ReadCString(addr, length); - break; - case Field::Module: - module = Memory::ReadCString(addr, length); - break; - case Field::Thread: - thread = Memory::ReadCString(addr, length); - break; - } + SCOPE_EXIT({ addr += length; }); - addr += length; - } + if (field == Field::Skip) { + continue; + } - // Empty log - nothing to do here - if (log_stream.str().empty() && message.empty()) { - return; + std::vector<u8> data(length); + Memory::ReadBlock(addr, data.data(), length); + fields.emplace(field, std::move(data)); } - // Format a nicely printable string out of the log metadata - if (!filename.empty()) { - log_stream << filename << ':'; - } - if (!module.empty()) { - log_stream << module << ':'; - } - if (!function.empty()) { - log_stream << function << ':'; - } - if (line) { - log_stream << std::to_string(line) << ':'; - } - if (!thread.empty()) { - log_stream << thread << ':'; - } - if (log_stream.str().length() > 0 && log_stream.str().back() == ':') { - log_stream << ' '; - } - log_stream << message; - - if (header.IsTailLog()) { - switch (header.severity) { - case MessageHeader::Severity::Trace: - LOG_DEBUG(Debug_Emulated, "{}", log_stream.str()); - break; - case MessageHeader::Severity::Info: - LOG_INFO(Debug_Emulated, "{}", log_stream.str()); - break; - case MessageHeader::Severity::Warning: - LOG_WARNING(Debug_Emulated, "{}", log_stream.str()); - break; - case MessageHeader::Severity::Error: - LOG_ERROR(Debug_Emulated, "{}", log_stream.str()); - break; - case MessageHeader::Severity::Critical: - LOG_CRITICAL(Debug_Emulated, "{}", log_stream.str()); - break; - } - } + manager.Log({header, std::move(fields)}); } - // This service function is intended to be used as a way to - // redirect logging output to different destinations, however, - // given we always want to see the logging output, it's sufficient - // to do nothing and return success here. void SetDestination(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_LM, "called"); + IPC::RequestParser rp{ctx}; + const auto destination = rp.PopEnum<DestinationFlag>(); + + LOG_DEBUG(Service_LM, "called, destination={:08X}", static_cast<u32>(destination)); + + manager.SetDestination(destination); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - std::ostringstream log_stream; + Manager& manager; }; class LM final : public ServiceFramework<LM> { public: - explicit LM() : ServiceFramework{"lm"} { + explicit LM(Manager& manager) : ServiceFramework{"lm"}, manager(manager) { + // clang-format off static const FunctionInfo functions[] = { - {0x00000000, &LM::OpenLogger, "OpenLogger"}, + {0, &LM::OpenLogger, "OpenLogger"}, }; + // clang-format on + RegisterHandlers(functions); } - /** - * LM::OpenLogger service function - * Inputs: - * 0: 0x00000000 - * Outputs: - * 0: ResultCode - */ +private: void OpenLogger(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_LM, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILogger>(); + rb.PushIpcInterface<ILogger>(manager); } + + Manager& manager; }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<LM>()->InstallAsService(service_manager); +void InstallInterfaces(Core::System& system) { + std::make_shared<LM>(system.GetLogManager())->InstallAsService(system.ServiceManager()); } } // namespace Service::LM diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h index 7806ae27b..d40410b5c 100644 --- a/src/core/hle/service/lm/lm.h +++ b/src/core/hle/service/lm/lm.h @@ -4,13 +4,13 @@ #pragma once -namespace Service::SM { -class ServiceManager; +namespace Core { +class System; } namespace Service::LM { /// Registers all LM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::LM diff --git a/src/core/hle/service/lm/manager.cpp b/src/core/hle/service/lm/manager.cpp new file mode 100644 index 000000000..b67081b86 --- /dev/null +++ b/src/core/hle/service/lm/manager.cpp @@ -0,0 +1,133 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/hle/service/lm/manager.h" +#include "core/reporter.h" + +namespace Service::LM { + +std::ostream& operator<<(std::ostream& os, DestinationFlag dest) { + std::vector<std::string> array; + const auto check_single_flag = [dest, &array](DestinationFlag check, std::string name) { + if ((static_cast<u32>(check) & static_cast<u32>(dest)) != 0) { + array.emplace_back(std::move(name)); + } + }; + + check_single_flag(DestinationFlag::Default, "Default"); + check_single_flag(DestinationFlag::UART, "UART"); + check_single_flag(DestinationFlag::UARTSleeping, "UART (Sleeping)"); + + os << "["; + for (const auto& entry : array) { + os << entry << ", "; + } + return os << "]"; +} + +std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity) { + switch (severity) { + case MessageHeader::Severity::Trace: + return os << "Trace"; + case MessageHeader::Severity::Info: + return os << "Info"; + case MessageHeader::Severity::Warning: + return os << "Warning"; + case MessageHeader::Severity::Error: + return os << "Error"; + case MessageHeader::Severity::Critical: + return os << "Critical"; + default: + return os << fmt::format("{:08X}", static_cast<u32>(severity)); + } +} + +std::ostream& operator<<(std::ostream& os, Field field) { + switch (field) { + case Field::Skip: + return os << "Skip"; + case Field::Message: + return os << "Message"; + case Field::Line: + return os << "Line"; + case Field::Filename: + return os << "Filename"; + case Field::Function: + return os << "Function"; + case Field::Module: + return os << "Module"; + case Field::Thread: + return os << "Thread"; + default: + return os << fmt::format("{:08X}", static_cast<u32>(field)); + } +} + +std::string FormatField(Field type, const std::vector<u8>& data) { + switch (type) { + case Field::Skip: + return ""; + case Field::Line: + if (data.size() >= sizeof(u32)) { + u32 line; + std::memcpy(&line, data.data(), sizeof(u32)); + return fmt::format("{}", line); + } + return "[ERROR DECODING LINE NUMBER]"; + case Field::Message: + case Field::Filename: + case Field::Function: + case Field::Module: + case Field::Thread: + return Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(data.data()), data.size()); + default: + UNIMPLEMENTED(); + } +} + +Manager::Manager(Core::Reporter& reporter) : reporter(reporter) {} + +Manager::~Manager() = default; + +void Manager::SetEnabled(bool enabled) { + this->enabled = enabled; +} + +void Manager::SetDestination(DestinationFlag destination) { + this->destination = destination; +} + +void Manager::Log(LogMessage message) { + if (message.header.IsHeadLog()) { + InitializeLog(); + } + + current_log.emplace_back(std::move(message)); + + if (current_log.back().header.IsTailLog()) { + FinalizeLog(); + } +} + +void Manager::Flush() { + FinalizeLog(); +} + +void Manager::InitializeLog() { + current_log.clear(); + + LOG_INFO(Service_LM, "Initialized new log session"); +} + +void Manager::FinalizeLog() { + reporter.SaveLogReport(static_cast<u32>(destination), std::move(current_log)); + + LOG_INFO(Service_LM, "Finalized current log session"); +} + +} // namespace Service::LM diff --git a/src/core/hle/service/lm/manager.h b/src/core/hle/service/lm/manager.h new file mode 100644 index 000000000..544e636ba --- /dev/null +++ b/src/core/hle/service/lm/manager.h @@ -0,0 +1,106 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <ostream> +#include <vector> +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Core { +class Reporter; +} + +namespace Service::LM { + +enum class DestinationFlag : u32 { + Default = 1, + UART = 2, + UARTSleeping = 4, + + All = 0xFFFF, +}; + +struct MessageHeader { + enum Flags : u32_le { + IsHead = 1, + IsTail = 2, + }; + enum Severity : u32_le { + Trace, + Info, + Warning, + Error, + Critical, + }; + + u64_le pid; + u64_le thread_context; + union { + BitField<0, 16, Flags> flags; + BitField<16, 8, Severity> severity; + BitField<24, 8, u32> verbosity; + }; + u32_le payload_size; + + bool IsHeadLog() const { + return flags & IsHead; + } + bool IsTailLog() const { + return flags & IsTail; + } +}; +static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size"); + +enum class Field : u8 { + Skip = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7, +}; + +std::ostream& operator<<(std::ostream& os, DestinationFlag dest); +std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity); +std::ostream& operator<<(std::ostream& os, Field field); + +using FieldMap = std::map<Field, std::vector<u8>>; + +struct LogMessage { + MessageHeader header; + FieldMap fields; +}; + +std::string FormatField(Field type, const std::vector<u8>& data); + +class Manager { +public: + explicit Manager(Core::Reporter& reporter); + ~Manager(); + + void SetEnabled(bool enabled); + void SetDestination(DestinationFlag destination); + + void Log(LogMessage message); + + void Flush(); + +private: + void InitializeLog(); + void FinalizeLog(); + + bool enabled = true; + DestinationFlag destination = DestinationFlag::All; + + std::vector<LogMessage> current_log; + + Core::Reporter& reporter; +}; + +} // namespace Service::LM diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index a42c22d44..aa886cd3e 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -18,8 +18,8 @@ namespace Service::NFP { namespace ErrCodes { -constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, - -1); // TODO(ogniK): Find the actual error code +[[maybe_unused]] constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, + -1); // TODO(ogniK): Find the actual error code constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152); } // namespace ErrCodes @@ -35,7 +35,7 @@ Module::Interface::~Interface() = default; class IUser final : public ServiceFramework<IUser> { public: IUser(Module::Interface& nfp_interface, Core::System& system) - : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) { + : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) { static const FunctionInfo functions[] = { {0, &IUser::Initialize, "Initialize"}, {1, &IUser::Finalize, "Finalize"}, @@ -183,6 +183,8 @@ private: case DeviceState::TagRemoved: device_state = DeviceState::Initialized; break; + default: + break; } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -324,7 +326,6 @@ private: Kernel::EventPair deactivate_event; Kernel::EventPair availability_change_event; const Module::Interface& nfp_interface; - Core::System& system; }; void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 24d1813a7..756a2af57 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -12,6 +12,13 @@ namespace Service::NIFM { +enum class RequestState : u32 { + NotSubmitted = 1, + Error = 1, ///< The duplicate 1 is intentional; it means both not submitted and error on HW. + Pending = 2, + Connected = 3, +}; + class IScanRequest final : public ServiceFramework<IScanRequest> { public: explicit IScanRequest() : ServiceFramework("IScanRequest") { @@ -81,7 +88,7 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + rb.PushEnum(RequestState::Connected); } void GetResult(Kernel::HLERequestContext& ctx) { @@ -189,14 +196,14 @@ private: IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u8>(0); + rb.Push<u8>(1); } void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u8>(0); + rb.Push<u8>(1); } Core::System& system; }; diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 7dcdb4a07..f64535237 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -324,14 +324,14 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { // Map backing memory for the font data LOG_DEBUG(Service_NS, "called"); - Core::CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, - SHARED_FONT_MEM_SIZE, - Kernel::MemoryState::Shared); + system.CurrentProcess()->VMManager().MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, + SHARED_FONT_MEM_SIZE, + Kernel::MemoryState::Shared); // Create shared font memory object auto& kernel = system.Kernel(); impl->shared_font_mem = Kernel::SharedMemory::Create( - kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, + kernel, system.CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, "PL_U:shared_font_mem"); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 6bc053f27..07c88465e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -45,6 +45,8 @@ u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std: return GetVARegions(input, output); case IoctlCommand::IocUnmapBufferCommand: return UnmapBuffer(input, output); + default: + break; } if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index ff6b1abae..eb88fee1b 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -38,9 +38,10 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::v return IocCtrlEventUnregister(input, output); case IoctlCommand::IocCtrlEventSignalCommand: return IocCtrlEventSignal(input, output); + default: + UNIMPLEMENTED_MSG("Unimplemented ioctl"); + return 0; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; } u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 389ace76f..cc2192e5c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -40,9 +40,10 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, return FlushL2(input, output); case IoctlCommand::IocGetGpuTime: return GetGpuTime(input, output); + default: + UNIMPLEMENTED_MSG("Unimplemented ioctl"); + return 0; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; } u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 2b8d1bef6..9de0ace22 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -44,6 +44,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve return GetWaitbase(input, output); case IoctlCommand::IocChannelSetTimeoutCommand: return ChannelSetTimeout(input, output); + default: + break; } if (command.group == NVGPU_IOCTL_MAGIC) { diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 831a427de..7c5302017 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { AOC::InstallInterfaces(*sm, system); APM::InstallInterfaces(system); Audio::InstallInterfaces(*sm, system); - BCAT::InstallInterfaces(*sm); + BCAT::InstallInterfaces(system); BPC::InstallInterfaces(*sm); BtDrv::InstallInterfaces(*sm, system); BTM::InstallInterfaces(*sm, system); @@ -226,7 +226,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { LBL::InstallInterfaces(*sm); LDN::InstallInterfaces(*sm); LDR::InstallInterfaces(*sm, system); - LM::InstallInterfaces(*sm); + LM::InstallInterfaces(system); Migration::InstallInterfaces(*sm); Mii::InstallInterfaces(*sm); MM::InstallInterfaces(*sm); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e75c700ad..f629892ae 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -150,6 +150,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, // Apply cheats if they exist and the program has a valid title ID if (pm) { auto& system = Core::System::GetInstance(); + system.SetCurrentProcessBuildID(nso_header.build_id); const auto cheats = pm->CreateCheatList(system, nso_header.build_id); if (!cheats.empty()) { system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9e030789d..fa49f3dd0 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -146,7 +146,7 @@ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { * using a VMA from the current process. */ static u8* GetPointerFromVMA(VAddr vaddr) { - return GetPointerFromVMA(*Core::CurrentProcess(), vaddr); + return GetPointerFromVMA(*Core::System::GetInstance().CurrentProcess(), vaddr); } template <typename T> @@ -226,7 +226,7 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { } bool IsValidVirtualAddress(const VAddr vaddr) { - return IsValidVirtualAddress(*Core::CurrentProcess(), vaddr); + return IsValidVirtualAddress(*Core::System::GetInstance().CurrentProcess(), vaddr); } bool IsKernelVirtualAddress(const VAddr vaddr) { @@ -387,7 +387,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ } void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { - ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); + ReadBlock(*Core::System::GetInstance().CurrentProcess(), src_addr, dest_buffer, size); } void Write8(const VAddr addr, const u8 data) { @@ -450,7 +450,7 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi } void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { - WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); + WriteBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_buffer, size); } void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) { @@ -539,7 +539,7 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, } void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { - CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); + CopyBlock(*Core::System::GetInstance().CurrentProcess(), dest_addr, src_addr, size); } } // namespace Memory diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 9c657929e..6f4af77fd 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -7,6 +7,7 @@ #include <fmt/chrono.h> #include <fmt/format.h> +#include <fmt/ostream.h> #include <json.hpp> #include "common/file_util.h" @@ -17,6 +18,7 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/process.h" #include "core/hle/result.h" +#include "core/hle/service/lm/manager.h" #include "core/reporter.h" #include "core/settings.h" @@ -354,6 +356,55 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result, SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); } +void Reporter::SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = + GetReportCommonData(system.CurrentProcess()->GetTitleID(), RESULT_SUCCESS, timestamp); + + out["log_destination"] = + fmt::format("{}", static_cast<Service::LM::DestinationFlag>(destination)); + + auto json_messages = json::array(); + std::transform(messages.begin(), messages.end(), std::back_inserter(json_messages), + [](const Service::LM::LogMessage& message) { + json out; + out["is_head"] = fmt::format("{}", message.header.IsHeadLog()); + out["is_tail"] = fmt::format("{}", message.header.IsTailLog()); + out["pid"] = fmt::format("{:016X}", message.header.pid); + out["thread_context"] = + fmt::format("{:016X}", message.header.thread_context); + out["payload_size"] = fmt::format("{:016X}", message.header.payload_size); + out["flags"] = fmt::format("{:04X}", message.header.flags.Value()); + out["severity"] = fmt::format("{}", message.header.severity.Value()); + out["verbosity"] = fmt::format("{:02X}", message.header.verbosity); + + auto fields = json::array(); + std::transform(message.fields.begin(), message.fields.end(), + std::back_inserter(fields), [](const auto& kv) { + json out; + out["type"] = fmt::format("{}", kv.first); + out["data"] = + Service::LM::FormatField(kv.first, kv.second); + return out; + }); + + out["fields"] = std::move(fields); + return out; + }); + + out["log_messages"] = std::move(json_messages); + + SaveToFile(std::move(out), + GetPath("log_report", system.CurrentProcess()->GetTitleID(), timestamp)); +} + void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, std::string log_message) const { if (!IsReportingEnabled()) diff --git a/src/core/reporter.h b/src/core/reporter.h index f08aa11fb..380941b1b 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -20,6 +20,10 @@ namespace Service::FileSystem { enum class LogMode : u32; } +namespace Service::LM { +struct LogMessage; +} // namespace Service::LM + namespace Core { class System; @@ -29,18 +33,22 @@ public: explicit Reporter(System& system); ~Reporter(); + // Used by fatal services void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace, u32 backtrace_size, const std::string& arch, u32 unk10) const; + // Used by syscall svcBreak void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, std::optional<std::vector<u8>> resolved_buffer = {}) const; + // Used by HLE service handler void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, const std::string& name, const std::string& service_name) const; + // Used by stub applet implementation void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color, bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel, @@ -55,6 +63,7 @@ public: void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data, std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const; + // Used by error applet void SaveErrorReport(u64 title_id, ResultCode result, std::optional<std::string> custom_text_main = {}, std::optional<std::string> custom_text_detail = {}) const; @@ -62,6 +71,11 @@ public: void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, std::string log_message) const; + // Used by lm services + void SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const; + + // Can be used anywhere to generate a backtrace and general info report at any point during + // execution. Not intended to be used for anything other than debugging or testing. void SaveUserReport() const; private: diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 7de3fd1e5..d1fc94060 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -103,6 +103,8 @@ void LogSettings() { LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub); LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port); LogSetting("Debugging_ProgramArgs", Settings::values.program_args); + LogSetting("Services_BCATBackend", Settings::values.bcat_backend); + LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local); } } // namespace Settings diff --git a/src/core/settings.h b/src/core/settings.h index 47bddfb30..9c98a9287 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -448,6 +448,10 @@ struct Values { bool reporting_services; bool quest_flag; + // BCAT + std::string bcat_backend; + bool bcat_boxcat_local; + // WebService bool enable_telemetry; std::string web_api_url; |