diff options
Diffstat (limited to 'src')
83 files changed, 2493 insertions, 398 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index da50a0bbc..e6f38d600 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -107,6 +107,11 @@ Stream::State AudioRenderer::GetStreamState() const { return stream->GetState(); } +static constexpr u32 VersionFromRevision(u32_le rev) { + // "REV7" -> 7 + return ((rev >> 24) & 0xff) - 0x30; +} + std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { // Copy UpdateDataHeader struct UpdateDataHeader config{}; @@ -166,6 +171,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ // Copy output header UpdateDataHeader response_data{worker_params}; std::vector<u8> output_params(response_data.total_size); + const auto audren_revision = VersionFromRevision(config.revision); + if (audren_revision >= 5) { + response_data.frame_count = 0x10; + response_data.total_size += 0x10; + } std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); // Copy output memory pool entries diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index 45afbe759..4f14b91cd 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -194,21 +194,24 @@ struct UpdateDataHeader { mixes_size = 0x0; sinks_size = config.sink_count * 0x20; performance_manager_size = 0x10; + frame_count = 0; total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + effects_size + sinks_size + performance_manager_size; } - u32_le revision; - u32_le behavior_size; - u32_le memory_pools_size; - u32_le voices_size; - u32_le voice_resource_size; - u32_le effects_size; - u32_le mixes_size; - u32_le sinks_size; - u32_le performance_manager_size; - INSERT_PADDING_WORDS(6); - u32_le total_size; + u32_le revision{}; + u32_le behavior_size{}; + u32_le memory_pools_size{}; + u32_le voices_size{}; + u32_le voice_resource_size{}; + u32_le effects_size{}; + u32_le mixes_size{}; + u32_le sinks_size{}; + u32_le performance_manager_size{}; + INSERT_PADDING_WORDS(1); + u32_le frame_count{}; + INSERT_PADDING_WORDS(4); + u32_le total_size{}; }; static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 01abdb3bb..dfed8b51d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -10,6 +10,9 @@ if (DEFINED ENV{CI}) elseif(DEFINED ENV{APPVEYOR}) set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) + elseif(DEFINED ENV{AZURE}) + set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME}) + set(BUILD_TAG $ENV{AZURE_REPO_TAG}) endif() endif() add_custom_command(OUTPUT scm_rev.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a6b56c9c6..3416854db 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,3 +1,9 @@ +if (YUZU_ENABLE_BOXCAT) + set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h) +else() + set(BCAT_BOXCAT_ADDITIONAL_SOURCES) +endif() + add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp @@ -82,6 +88,8 @@ add_library(core STATIC file_sys/vfs_concat.h file_sys/vfs_layered.cpp file_sys/vfs_layered.h + file_sys/vfs_libzip.cpp + file_sys/vfs_libzip.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp @@ -241,6 +249,9 @@ add_library(core STATIC hle/service/audio/errors.h hle/service/audio/hwopus.cpp hle/service/audio/hwopus.h + hle/service/bcat/backend/backend.cpp + hle/service/bcat/backend/backend.h + ${BCAT_BOXCAT_ADDITIONAL_SOURCES} hle/service/bcat/bcat.cpp hle/service/bcat/bcat.h hle/service/bcat/module.cpp @@ -499,6 +510,15 @@ create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives) + +if (YUZU_ENABLE_BOXCAT) + get_directory_property(OPENSSL_LIBS + DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl + DEFINITION OPENSSL_LIBS) + target_compile_definitions(core PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT -DYUZU_ENABLE_BOXCAT) + target_link_libraries(core PRIVATE httplib json-headers ${OPENSSL_LIBS} zip) +endif() + if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) target_link_libraries(core PRIVATE web_service) diff --git a/src/core/core.cpp b/src/core/core.cpp index 76bb2bae9..75a7ffb97 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -163,6 +163,7 @@ struct System::Impl { gpu_core = VideoCore::CreateGPU(system); is_powered_on = true; + exit_lock = false; LOG_DEBUG(Core, "Initialized OK"); @@ -249,6 +250,7 @@ struct System::Impl { perf_stats->GetMeanFrametime()); is_powered_on = false; + exit_lock = false; // Shutdown emulation session renderer.reset(); @@ -333,9 +335,11 @@ struct System::Impl { std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager; CpuCoreManager cpu_core_manager; bool is_powered_on = false; + bool exit_lock = false; 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; @@ -629,6 +633,22 @@ const Service::APM::Controller& System::GetAPMController() const { return impl->apm_controller; } +void System::SetExitLock(bool locked) { + impl->exit_lock = locked; +} + +bool System::GetExitLock() const { + return impl->exit_lock; +} + +void System::SetCurrentProcessBuildID(std::array<u8, 32> id) { + impl->build_id = id; +} + +const std::array<u8, 32>& System::GetCurrentProcessBuildID() const { + return impl->build_id; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(*this, emu_window); } diff --git a/src/core/core.h b/src/core/core.h index d2a3c82d8..f49b7fbf9 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -326,6 +326,14 @@ public: const Service::APM::Controller& GetAPMController() const; + void SetExitLock(bool locked); + + bool GetExitLock() const; + + void SetCurrentProcessBuildID(std::array<u8, 0x20> id); + + const std::array<u8, 0x20>& GetCurrentProcessBuildID() const; + private: System(); 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/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/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index a7c55e116..0c0f7ed6e 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -70,7 +70,7 @@ public: protected: void Get(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; ProfileData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { @@ -89,7 +89,7 @@ protected: } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; if (profile_manager.GetProfileBase(user_id, profile_base)) { IPC::ResponseBuilder rb{ctx, 16}; @@ -263,7 +263,7 @@ private: }; void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); @@ -272,7 +272,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; Common::UUID user_id = rp.PopRaw<Common::UUID>(); - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -280,21 +280,21 @@ void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { } void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); ctx.WriteBuffer(profile_manager->GetAllUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); ctx.WriteBuffer(profile_manager->GetOpenUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser()); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3366fd8ce..34409e0c3 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_} { @@ -232,12 +238,12 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { IDebugFunctions::~IDebugFunctions() = default; -ISelfController::ISelfController(Core::System& system_, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger_) - : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) { +ISelfController::ISelfController(Core::System& system, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Exit"}, + {0, &ISelfController::Exit, "Exit"}, {1, &ISelfController::LockExit, "LockExit"}, {2, &ISelfController::UnlockExit, "UnlockExit"}, {3, &ISelfController::EnterFatalSection, "EnterFatalSection"}, @@ -282,7 +288,7 @@ ISelfController::ISelfController(Core::System& system_, RegisterHandlers(functions); - auto& kernel = system_.Kernel(); + auto& kernel = system.Kernel(); launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "ISelfController:LaunchableEvent"); @@ -298,15 +304,28 @@ ISelfController::ISelfController(Core::System& system_, ISelfController::~ISelfController() = default; +void ISelfController::Exit(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + system.Shutdown(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.SetExitLock(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.SetExitLock(false); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -550,6 +569,10 @@ void AppletMessageQueue::OperationModeChanged() { on_operation_mode_changed.writable->Signal(); } +void AppletMessageQueue::RequestExit() { + PushMessage(AppletMessage::ExitRequested); +} + ICommonStateGetter::ICommonStateGetter(Core::System& system, std::shared_ptr<AppletMessageQueue> msg_queue) : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) { @@ -1111,26 +1134,55 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx } void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_AM, "called"); + IPC::RequestParser rp{ctx}; + const auto kind = rp.PopEnum<LaunchParameterKind>(); - LaunchParameters params{}; + LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind)); - params.magic = POP_LAUNCH_PARAMETER_MAGIC; - params.is_account_selected = 1; + if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { + const auto backend = BCAT::CreateBackendFromSettings( + [this](u64 tid) { return system.GetFileSystemController().GetBCATDirectory(tid); }); + const auto build_id_full = Core::System::GetInstance().GetCurrentProcessBuildID(); + u64 build_id{}; + std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); - Account::ProfileManager profile_manager{}; - const auto uuid = profile_manager.GetUser(Settings::values.current_user); - ASSERT(uuid); - params.current_user = uuid->uuid; + const auto data = + backend->GetLaunchParameter({Core::CurrentProcess()->GetTitleID(), build_id}); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + if (data.has_value()) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<AM::IStorage>(*data); + launch_popped_application_specific = true; + return; + } + } else if (kind == LaunchParameterKind::AccountPreselectedUser && + !launch_popped_account_preselect) { + LaunchParameterAccountPreselectedUser params{}; - rb.Push(RESULT_SUCCESS); + params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; + params.is_account_selected = 1; + + Account::ProfileManager profile_manager{}; + const auto uuid = profile_manager.GetUser(Settings::values.current_user); + ASSERT(uuid); + params.current_user = uuid->uuid; - std::vector<u8> buffer(sizeof(LaunchParameters)); - std::memcpy(buffer.data(), ¶ms, buffer.size()); + 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); + rb.PushIpcInterface<AM::IStorage>(buffer); + launch_popped_account_preselect = true; + return; + } + + LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NO_DATA_IN_CHANNEL); } void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 28f870302..9169eb2bd 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -45,6 +45,7 @@ class AppletMessageQueue { public: enum class AppletMessage : u32 { NoMessage = 0, + ExitRequested = 4, FocusStateChanged = 15, OperationModeChanged = 30, PerformanceModeChanged = 31, @@ -59,6 +60,7 @@ public: AppletMessage PopMessage(); std::size_t GetMessageCount() const; void OperationModeChanged(); + void RequestExit(); private: std::queue<AppletMessage> messages; @@ -123,6 +125,7 @@ public: ~ISelfController() override; private: + void Exit(Kernel::HLERequestContext& ctx); void LockExit(Kernel::HLERequestContext& ctx); void UnlockExit(Kernel::HLERequestContext& ctx); void EnterFatalSection(Kernel::HLERequestContext& ctx); @@ -151,6 +154,8 @@ 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> { @@ -250,6 +255,8 @@ private: void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); + bool launch_popped_application_specific = false; + bool launch_popped_account_preselect = false; Kernel::EventPair gpu_error_detected_event; Core::System& system; }; diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 0e0d10858..2e3e45915 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -19,6 +19,8 @@ class NVFlinger; namespace AM { +class AppletMessageQueue; + class AppletAE final : public ServiceFramework<AppletAE> { public: explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index 99a65e7b5..758da792d 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -19,6 +19,8 @@ class NVFlinger; namespace AM { +class AppletMessageQueue; + class AppletOE final : public ServiceFramework<AppletOE> { public: explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger, 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/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp new file mode 100644 index 000000000..9b677debe --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -0,0 +1,136 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/lock.h" +#include "core/hle/service/bcat/backend/backend.h" + +namespace Service::BCAT { + +ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} { + auto& kernel{Core::System::GetInstance().Kernel()}; + event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, "ProgressServiceBackend:UpdateEvent:" + event_name); +} + +Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() { + return event.readable; +} + +DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() { + return impl; +} + +void ProgressServiceBackend::SetNeedHLELock(bool need) { + need_hle_lock = need; +} + +void ProgressServiceBackend::SetTotalSize(u64 size) { + impl.total_bytes = size; + SignalUpdate(); +} + +void ProgressServiceBackend::StartConnecting() { + impl.status = DeliveryCacheProgressImpl::Status::Connecting; + SignalUpdate(); +} + +void ProgressServiceBackend::StartProcessingDataList() { + impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList; + SignalUpdate(); +} + +void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name, + std::string_view file_name, u64 file_size) { + impl.status = DeliveryCacheProgressImpl::Status::Downloading; + impl.current_downloaded_bytes = 0; + impl.current_total_bytes = file_size; + std::memcpy(impl.current_directory.data(), dir_name.data(), + std::min<u64>(dir_name.size(), 0x31ull)); + std::memcpy(impl.current_file.data(), file_name.data(), + std::min<u64>(file_name.size(), 0x31ull)); + SignalUpdate(); +} + +void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) { + impl.current_downloaded_bytes = downloaded; + SignalUpdate(); +} + +void ProgressServiceBackend::FinishDownloadingFile() { + impl.total_downloaded_bytes += impl.current_total_bytes; + SignalUpdate(); +} + +void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) { + impl.status = DeliveryCacheProgressImpl::Status::Committing; + impl.current_file.fill(0); + impl.current_downloaded_bytes = 0; + impl.current_total_bytes = 0; + std::memcpy(impl.current_directory.data(), dir_name.data(), + std::min<u64>(dir_name.size(), 0x31ull)); + SignalUpdate(); +} + +void ProgressServiceBackend::FinishDownload(ResultCode result) { + impl.total_downloaded_bytes = impl.total_bytes; + impl.status = DeliveryCacheProgressImpl::Status::Done; + impl.result = result; + SignalUpdate(); +} + +void ProgressServiceBackend::SignalUpdate() const { + if (need_hle_lock) { + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + event.writable->Signal(); + } else { + event.writable->Signal(); + } +} + +Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {} + +Backend::~Backend() = default; + +NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(getter)) {} + +NullBackend::~NullBackend() = default; + +bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, + title.build_id); + + progress.FinishDownload(RESULT_SUCCESS); + return true; +} + +bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, + title.build_id, name); + + progress.FinishDownload(RESULT_SUCCESS); + return true; +} + +bool NullBackend::Clear(u64 title_id) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}"); + + return true; +} + +void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id, + Common::HexToString(passphrase)); +} + +std::optional<std::vector<u8>> NullBackend::GetLaunchParameter(TitleIDVersion title) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, + title.build_id); + return std::nullopt; +} + +} // namespace Service::BCAT 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..3f5d8b5dd --- /dev/null +++ b/src/core/hle/service/bcat/backend/backend.h @@ -0,0 +1,147 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/result.h" + +namespace Service::BCAT { + +struct DeliveryCacheProgressImpl; + +using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>; +using Passphrase = std::array<u8, 0x20>; + +struct TitleIDVersion { + u64 title_id; + u64 build_id; +}; + +using DirectoryName = std::array<char, 0x20>; +using FileName = std::array<char, 0x20>; + +struct DeliveryCacheProgressImpl { + enum class Status : s32 { + None = 0x0, + Queued = 0x1, + Connecting = 0x2, + ProcessingDataList = 0x3, + Downloading = 0x4, + Committing = 0x5, + Done = 0x9, + }; + + Status status; + ResultCode result = RESULT_SUCCESS; + DirectoryName current_directory; + FileName current_file; + s64 current_downloaded_bytes; ///< Bytes downloaded on current file. + s64 current_total_bytes; ///< Bytes total on current file. + s64 total_downloaded_bytes; ///< Bytes downloaded on overall download. + s64 total_bytes; ///< Bytes total on overall download. + INSERT_PADDING_BYTES( + 0x198); ///< Appears to be unused in official code, possibly reserved for future use. +}; +static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200, + "DeliveryCacheProgressImpl has incorrect size."); + +// A class to manage the signalling to the game about BCAT download progress. +// Some of this class is implemented in module.cpp to avoid exposing the implementation structure. +class ProgressServiceBackend { + friend class IBcatService; + +public: + // Clients should call this with true if any of the functions are going to be called from a + // non-HLE thread and this class need to lock the hle mutex. (default is false) + void SetNeedHLELock(bool need); + + // Sets the number of bytes total in the entire download. + void SetTotalSize(u64 size); + + // Notifies the application that the backend has started connecting to the server. + void StartConnecting(); + // Notifies the application that the backend has begun accumulating and processing metadata. + void StartProcessingDataList(); + + // Notifies the application that a file is starting to be downloaded. + void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size); + // Updates the progress of the current file to the size passed. + void UpdateFileProgress(u64 downloaded); + // Notifies the application that the current file has completed download. + void FinishDownloadingFile(); + + // Notifies the application that all files in this directory have completed and are being + // finalized. + void CommitDirectory(std::string_view dir_name); + + // Notifies the application that the operation completed with result code result. + void FinishDownload(ResultCode result); + +private: + explicit ProgressServiceBackend(std::string event_name); + + Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent(); + DeliveryCacheProgressImpl& GetImpl(); + + void SignalUpdate() const; + + DeliveryCacheProgressImpl impl; + Kernel::EventPair event; + bool need_hle_lock = false; +}; + +// A class representing an abstract backend for BCAT functionality. +class Backend { +public: + explicit Backend(DirectoryGetter getter); + virtual ~Backend(); + + // Called when the backend is needed to synchronize the data for the game with title ID and + // version in title. A ProgressServiceBackend object is provided to alert the application of + // status. + virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0; + // Very similar to Synchronize, but only for the directory provided. Backends should not alter + // the data for any other directories. + virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) = 0; + + // Removes all cached data associated with title id provided. + virtual bool Clear(u64 title_id) = 0; + + // Sets the BCAT Passphrase to be used with the associated title ID. + virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0; + + // Gets the launch parameter used by AM associated with the title ID and version provided. + virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0; + +protected: + DirectoryGetter dir_getter; +}; + +// A backend of BCAT that provides no operation. +class NullBackend : public Backend { +public: + explicit NullBackend(const DirectoryGetter& getter); + ~NullBackend() override; + + bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; + bool SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) override; + + bool Clear(u64 title_id) override; + + void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; + + std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; +}; + +std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter); + +} // namespace Service::BCAT 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..e6ee0810b --- /dev/null +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -0,0 +1,503 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <fmt/ostream.h> +#include <httplib.h> +#include <json.hpp> +#include <mbedtls/sha256.h> +#include "common/hex_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_libzip.h" +#include "core/file_sys/vfs_vector.h" +#include "core/frontend/applets/error.h" +#include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/bcat/backend/boxcat.h" +#include "core/settings.h" + +namespace { + +// Prevents conflicts with windows macro called CreateFile +FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) { + return dir->CreateFile(name); +} + +// Prevents conflicts with windows macro called DeleteFile +bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) { + return dir->DeleteFile(name); +} + +} // Anonymous namespace + +namespace Service::BCAT { + +constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1}; + +constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; + +// Formatted using fmt with arg[0] = hex title id +constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat"; +constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam"; + +constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events"; + +constexpr char BOXCAT_API_VERSION[] = "1"; +constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu"; + +// HTTP status codes for Boxcat +enum class ResponseStatus { + Ok = 200, ///< Operation completed successfully. + BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server. + NoUpdate = 304, ///< The digest provided would match the new data, no need to update. + NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation. + NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format + ///< issues or whatnot) and has no data. +}; + +enum class DownloadResult { + Success = 0, + NoResponse, + GeneralWebError, + NoMatchTitleId, + NoMatchBuildId, + InvalidContentType, + GeneralFSError, + BadClientVersion, +}; + +constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{ + "Success", + "There was no response from the server.", + "There was a general web error code returned from the server.", + "The title ID of the current game doesn't have a boxcat implementation. If you believe an " + "implementation should be added, contact yuzu support.", + "The build ID of the current version of the game is marked as incompatible with the current " + "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.", + "The content type of the web response was invalid.", + "There was a general filesystem error while saving the zip file.", + "The server is either too new or too old to serve the request. Try using the latest version of " + "an official release of yuzu.", +}; + +std::ostream& operator<<(std::ostream& os, DownloadResult result) { + return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result)); +} + +constexpr u32 PORT = 443; +constexpr u32 TIMEOUT_SECONDS = 30; +constexpr u64 VFS_COPY_BLOCK_SIZE = 1ull << 24; // 4MB + +namespace { + +std::string GetBINFilePath(u64 title_id) { + return fmt::format("{}bcat/{:016X}/launchparam.bin", + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); +} + +std::string GetZIPFilePath(u64 title_id) { + return fmt::format("{}bcat/{:016X}/data.zip", + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); +} + +// If the error is something the user should know about (build ID mismatch, bad client version), +// display an error. +void HandleDownloadDisplayResult(DownloadResult res) { + if (res == DownloadResult::Success || res == DownloadResult::NoResponse || + res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError || + res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) { + return; + } + + const auto& frontend{Core::System::GetInstance().GetAppletManager().GetAppletFrontendSet()}; + frontend.error->ShowCustomErrorText( + ResultCode(-1), "There was an error while attempting to use Boxcat.", + DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {}); +} + +bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest, + std::string_view dir_name, ProgressServiceBackend& progress, + std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + if (!dest->Resize(src->GetSize())) + return false; + + progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize()); + + std::vector<u8> temp(std::min(block_size, src->GetSize())); + for (std::size_t i = 0; i < src->GetSize(); i += block_size) { + const auto read = std::min(block_size, src->GetSize() - i); + + if (src->Read(temp.data(), read, i) != read) { + return false; + } + + if (dest->Write(temp.data(), read, i) != read) { + return false; + } + + progress.UpdateFileProgress(i); + } + + progress.FinishDownloadingFile(); + + return true; +} + +bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest, + ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& file : src->GetFiles()) { + const auto out_file = VfsCreateFileWrap(dest, file->GetName()); + if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) { + return false; + } + } + progress.CommitDirectory(src->GetName()); + + return true; +} + +bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest, + ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { + if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + return false; + + for (const auto& dir : src->GetSubdirectories()) { + const auto out = dest->CreateSubdirectory(dir->GetName()); + if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) { + return false; + } + } + + return true; +} + +} // Anonymous namespace + +class Boxcat::Client { +public: + Client(std::string path, u64 title_id, u64 build_id) + : path(std::move(path)), title_id(title_id), build_id(build_id) {} + + DownloadResult DownloadDataZip() { + return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS, + "application/zip"); + } + + DownloadResult DownloadLaunchParam() { + return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id), + TIMEOUT_SECONDS / 3, "application/octet-stream"); + } + +private: + DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, + const std::string& content_type_name) { + if (client == nullptr) { + client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds); + } + + httplib::Headers headers{ + {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, + {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, + {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)}, + }; + + if (FileUtil::Exists(path)) { + FileUtil::IOFile file{path, "rb"}; + if (file.IsOpen()) { + std::vector<u8> bytes(file.GetSize()); + file.ReadBytes(bytes.data(), bytes.size()); + const auto digest = DigestFile(bytes); + headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); + } + } + + const auto response = client->Get(resolved_path.c_str(), headers); + if (response == nullptr) + return DownloadResult::NoResponse; + + if (response->status == static_cast<int>(ResponseStatus::NoUpdate)) + return DownloadResult::Success; + if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) + return DownloadResult::BadClientVersion; + if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId)) + return DownloadResult::NoMatchTitleId; + if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) + return DownloadResult::NoMatchBuildId; + if (response->status != static_cast<int>(ResponseStatus::Ok)) + return DownloadResult::GeneralWebError; + + const auto content_type = response->headers.find("content-type"); + if (content_type == response->headers.end() || + content_type->second.find(content_type_name) == std::string::npos) { + return DownloadResult::InvalidContentType; + } + + FileUtil::CreateFullPath(path); + FileUtil::IOFile file{path, "wb"}; + if (!file.IsOpen()) + return DownloadResult::GeneralFSError; + if (!file.Resize(response->body.size())) + return DownloadResult::GeneralFSError; + if (file.WriteBytes(response->body.data(), response->body.size()) != response->body.size()) + return DownloadResult::GeneralFSError; + + return DownloadResult::Success; + } + + using Digest = std::array<u8, 0x20>; + static Digest DigestFile(std::vector<u8> bytes) { + Digest out{}; + mbedtls_sha256(bytes.data(), bytes.size(), out.data(), 0); + return out; + } + + std::unique_ptr<httplib::Client> client; + std::string path; + u64 title_id; + u64 build_id; +}; + +Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {} + +Boxcat::~Boxcat() = default; + +void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, + ProgressServiceBackend& progress, + std::optional<std::string> dir_name = {}) { + progress.SetNeedHLELock(true); + + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); + const auto dir = dir_getter(title.title_id); + if (dir) + progress.SetTotalSize(dir->GetSize()); + progress.FinishDownload(RESULT_SUCCESS); + return; + } + + const auto zip_path{GetZIPFilePath(title.title_id)}; + Boxcat::Client client{zip_path, title.title_id, title.build_id}; + + progress.StartConnecting(); + + const auto res = client.DownloadDataZip(); + if (res != DownloadResult::Success) { + LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); + + if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { + FileUtil::Delete(zip_path); + } + + HandleDownloadDisplayResult(res); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + progress.StartProcessingDataList(); + + FileUtil::IOFile zip{zip_path, "rb"}; + const auto size = zip.GetSize(); + std::vector<u8> bytes(size); + if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { + LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes)); + if (extracted == nullptr) { + LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + if (dir_name == std::nullopt) { + progress.SetTotalSize(extracted->GetSize()); + + const auto target_dir = dir_getter(title.title_id); + if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) { + LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + } else { + const auto target_dir = dir_getter(title.title_id); + if (target_dir == nullptr) { + LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + + const auto target_sub = target_dir->GetSubdirectory(*dir_name); + const auto source_sub = extracted->GetSubdirectory(*dir_name); + + progress.SetTotalSize(source_sub->GetSize()); + + std::vector<std::string> filenames; + { + const auto files = target_sub->GetFiles(); + std::transform(files.begin(), files.end(), std::back_inserter(filenames), + [](const auto& vfile) { return vfile->GetName(); }); + } + + for (const auto& filename : filenames) { + VfsDeleteFileWrap(target_sub, filename); + } + + if (target_sub == nullptr || source_sub == nullptr || + !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) { + LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); + progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); + return; + } + } + + progress.FinishDownload(RESULT_SUCCESS); +} + +bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { + is_syncing.exchange(true); + std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); }) + .detach(); + return true; +} + +bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name, + ProgressServiceBackend& progress) { + is_syncing.exchange(true); + std::thread( + [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); }) + .detach(); + return true; +} + +bool Boxcat::Clear(u64 title_id) { + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear."); + return true; + } + + const auto dir = dir_getter(title_id); + + std::vector<std::string> dirnames; + + for (const auto& subdir : dir->GetSubdirectories()) + dirnames.push_back(subdir->GetName()); + + for (const auto& subdir : dirnames) { + if (!dir->DeleteSubdirectoryRecursive(subdir)) + return false; + } + + return true; +} + +void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, + Common::HexToString(passphrase)); +} + +std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { + const auto path{GetBINFilePath(title.title_id)}; + + if (Settings::values.bcat_boxcat_local) { + LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); + } else { + Boxcat::Client client{path, title.title_id, title.build_id}; + + const auto res = client.DownloadLaunchParam(); + if (res != DownloadResult::Success) { + LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); + + if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { + FileUtil::Delete(path); + } + + HandleDownloadDisplayResult(res); + return std::nullopt; + } + } + + FileUtil::IOFile bin{path, "rb"}; + const auto size = bin.GetSize(); + std::vector<u8> bytes(size); + if (!bin.IsOpen() || size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { + LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", + path); + return std::nullopt; + } + + return bytes; +} + +Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, + std::map<std::string, EventStatus>& games) { + httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), + static_cast<int>(TIMEOUT_SECONDS)}; + + httplib::Headers headers{ + {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, + {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, + }; + + const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); + if (response == nullptr) + return StatusResult::Offline; + + if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) + return StatusResult::BadClientVersion; + + try { + nlohmann::json json = nlohmann::json::parse(response->body); + + if (!json["online"].get<bool>()) + return StatusResult::Offline; + + if (json["global"].is_null()) + global = std::nullopt; + else + global = json["global"].get<std::string>(); + + if (json["games"].is_array()) { + for (const auto object : json["games"]) { + if (object.is_object() && object.find("name") != object.end()) { + EventStatus detail{}; + if (object["header"].is_string()) { + detail.header = object["header"].get<std::string>(); + } else { + detail.header = std::nullopt; + } + + if (object["footer"].is_string()) { + detail.footer = object["footer"].get<std::string>(); + } else { + detail.footer = std::nullopt; + } + + if (object["events"].is_array()) { + for (const auto& event : object["events"]) { + if (!event.is_string()) + continue; + detail.events.push_back(event.get<std::string>()); + } + } + + games.insert_or_assign(object["name"], std::move(detail)); + } + } + } + + return StatusResult::Success; + } catch (const nlohmann::json::parse_error& e) { + return StatusResult::ParseError; + } +} + +} // namespace Service::BCAT 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..c2f946424 100644 --- a/src/core/hle/service/bcat/bcat.cpp +++ b/src/core/hle/service/bcat/bcat.cpp @@ -6,11 +6,15 @@ namespace Service::BCAT { -BCAT::BCAT(std::shared_ptr<Module> module, const char* name) - : Module::Interface(std::move(module), name) { +BCAT::BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, const char* name) + : Module::Interface(std::move(module), fsc, name) { + // clang-format off static const FunctionInfo functions[] = { {0, &BCAT::CreateBcatService, "CreateBcatService"}, + {1, &BCAT::CreateDeliveryCacheStorageService, "CreateDeliveryCacheStorageService"}, + {2, &BCAT::CreateDeliveryCacheStorageServiceWithApplicationId, "CreateDeliveryCacheStorageServiceWithApplicationId"}, }; + // clang-format on RegisterHandlers(functions); } diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index 802bd689a..813073658 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -10,7 +10,8 @@ namespace Service::BCAT { class BCAT final : public Module::Interface { public: - explicit BCAT(std::shared_ptr<Module> module, const char* name); + explicit BCAT(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, + const char* name); ~BCAT() override; }; diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index b7bd738fc..b3fed56c7 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -2,34 +2,254 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cctype> +#include <mbedtls/md5.h> +#include "backend/boxcat.h" +#include "common/hex_util.h" #include "common/logging/log.h" +#include "common/string_util.h" +#include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/bcat/backend/backend.h" #include "core/hle/service/bcat/bcat.h" #include "core/hle/service/bcat/module.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/settings.h" namespace Service::BCAT { +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1}; +constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2}; +constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6}; +constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7}; + +// The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files +// and if any of them have a non-zero result it just forwards that result. This is the FS error code +// for permission denied, which is the closest approximation of this scenario. +constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400}; + +using BCATDigest = std::array<u8, 0x10>; + +namespace { + +u64 GetCurrentBuildID() { + const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID(); + u64 out{}; + std::memcpy(&out, id.data(), sizeof(u64)); + return out; +} + +// The digest is only used to determine if a file is unique compared to others of the same name. +// Since the algorithm isn't ever checked in game, MD5 is safe. +BCATDigest DigestFile(const FileSys::VirtualFile& file) { + BCATDigest out{}; + const auto bytes = file->ReadAllBytes(); + mbedtls_md5(bytes.data(), bytes.size(), out.data()); + return out; +} + +// For a name to be valid it must be non-empty, must have a null terminating character as the final +// char, can only contain numbers, letters, underscores and a hyphen if directory and a period if +// file. +bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name, + char match_char) { + const auto null_chars = std::count(name.begin(), name.end(), 0); + const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) { + return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0'; + }); + if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') { + LOG_ERROR(Service_BCAT, "Name passed was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return false; + } + + return true; +} + +bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) { + return VerifyNameValidInternal(ctx, name, '-'); +} + +bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) { + return VerifyNameValidInternal(ctx, name, '.'); +} + +} // Anonymous namespace + +struct DeliveryCacheDirectoryEntry { + FileName name; + u64 size; + BCATDigest digest; +}; + +class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> { +public: + IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event, + const DeliveryCacheProgressImpl& impl) + : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, + {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + } + + void GetImpl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Kernel::SharedPtr<Kernel::ReadableEvent> event; + const DeliveryCacheProgressImpl& impl; +}; + class IBcatService final : public ServiceFramework<IBcatService> { public: - IBcatService() : ServiceFramework("IBcatService") { + IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) { + // clang-format off static const FunctionInfo functions[] = { - {10100, nullptr, "RequestSyncDeliveryCache"}, - {10101, nullptr, "RequestSyncDeliveryCacheWithDirectoryName"}, + {10100, &IBcatService::RequestSyncDeliveryCache, "RequestSyncDeliveryCache"}, + {10101, &IBcatService::RequestSyncDeliveryCacheWithDirectoryName, "RequestSyncDeliveryCacheWithDirectoryName"}, {10200, nullptr, "CancelSyncDeliveryCacheRequest"}, {20100, nullptr, "RequestSyncDeliveryCacheWithApplicationId"}, {20101, nullptr, "RequestSyncDeliveryCacheWithApplicationIdAndDirectoryName"}, - {30100, nullptr, "SetPassphrase"}, + {30100, &IBcatService::SetPassphrase, "SetPassphrase"}, {30200, nullptr, "RegisterBackgroundDeliveryTask"}, {30201, nullptr, "UnregisterBackgroundDeliveryTask"}, {30202, nullptr, "BlockDeliveryTask"}, {30203, nullptr, "UnblockDeliveryTask"}, {90100, nullptr, "EnumerateBackgroundDeliveryTask"}, {90200, nullptr, "GetDeliveryList"}, - {90201, nullptr, "ClearDeliveryCacheStorage"}, + {90201, &IBcatService::ClearDeliveryCacheStorage, "ClearDeliveryCacheStorage"}, {90300, nullptr, "GetPushNotificationLog"}, }; + // clang-format on RegisterHandlers(functions); } + +private: + enum class SyncType { + Normal, + Directory, + Count, + }; + + std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) { + auto& backend{progress.at(static_cast<std::size_t>(type))}; + return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(), + backend.GetImpl()); + } + + void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()}, + progress.at(static_cast<std::size_t>(SyncType::Normal))); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(CreateProgressService(SyncType::Normal)); + } + + void RequestSyncDeliveryCacheWithDirectoryName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw<DirectoryName>(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()}, + name, + progress.at(static_cast<std::size_t>(SyncType::Directory))); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(CreateProgressService(SyncType::Directory)); + } + + void SetPassphrase(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + const auto passphrase_raw = ctx.ReadBuffer(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, + Common::HexToString(passphrase_raw)); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + } + + if (passphrase_raw.size() > 0x40) { + LOG_ERROR(Service_BCAT, "Passphrase too large!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Passphrase passphrase{}; + std::memcpy(passphrase.data(), passphrase_raw.data(), + std::min(passphrase.size(), passphrase_raw.size())); + + backend.SetPassphrase(title_id, passphrase); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ClearDeliveryCacheStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + if (title_id == 0) { + LOG_ERROR(Service_BCAT, "Invalid title ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + if (!backend.Clear(title_id)) { + LOG_ERROR(Service_BCAT, "Could not clear the directory successfully!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_CLEAR_CACHE); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + Backend& backend; + + std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{ + ProgressServiceBackend{"Normal"}, + ProgressServiceBackend{"Directory"}, + }; }; void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { @@ -37,20 +257,331 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IBcatService>(); + rb.PushIpcInterface<IBcatService>(*backend); +} + +class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> { +public: + IDeliveryCacheFileService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheFileService::Open, "Open"}, + {1, &IDeliveryCacheFileService::Read, "Read"}, + {2, &IDeliveryCacheFileService::GetSize, "GetSize"}, + {3, &IDeliveryCacheFileService::GetDigest, "GetDigest"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto dir_name_raw = rp.PopRaw<DirectoryName>(); + const auto file_name_raw = rp.PopRaw<FileName>(); + + const auto dir_name = + Common::StringFromFixedZeroTerminatedBuffer(dir_name_raw.data(), dir_name_raw.size()); + const auto file_name = + Common::StringFromFixedZeroTerminatedBuffer(file_name_raw.data(), file_name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, dir_name={}, file_name={}", dir_name, file_name); + + if (!VerifyNameValidDir(ctx, dir_name_raw) || !VerifyNameValidFile(ctx, file_name_raw)) + return; + + if (current_file != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + const auto dir = root->GetSubdirectory(dir_name); + + if (dir == nullptr) { + LOG_ERROR(Service_BCAT, "The directory of name={} couldn't be opened!", dir_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + current_file = dir->GetFile(file_name); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "The file of name={} couldn't be opened!", file_name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto offset{rp.PopRaw<u64>()}; + + auto size = ctx.GetWriteBufferSize(); + + LOG_DEBUG(Service_BCAT, "called, offset={:016X}, size={:016X}", offset, size); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + size = std::min<u64>(current_file->GetSize() - offset, size); + const auto buffer = current_file->ReadBytes(size, offset); + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(buffer.size()); + } + + void GetSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(current_file->GetSize()); + } + + void GetDigest(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_file == nullptr) { + LOG_ERROR(Service_BCAT, "There is no file currently open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(DigestFile(current_file)); + } + + FileSys::VirtualDir root; + FileSys::VirtualFile current_file; +}; + +class IDeliveryCacheDirectoryService final + : public ServiceFramework<IDeliveryCacheDirectoryService> { +public: + IDeliveryCacheDirectoryService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheDirectoryService::Open, "Open"}, + {1, &IDeliveryCacheDirectoryService::Read, "Read"}, + {2, &IDeliveryCacheDirectoryService::GetCount, "GetCount"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Open(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto name_raw = rp.PopRaw<DirectoryName>(); + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(name_raw.data(), name_raw.size()); + + LOG_DEBUG(Service_BCAT, "called, name={}", name); + + if (!VerifyNameValidDir(ctx, name_raw)) + return; + + if (current_dir != nullptr) { + LOG_ERROR(Service_BCAT, "A file has already been opened on this interface!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ENTITY_ALREADY_OPEN); + return; + } + + current_dir = root->GetSubdirectory(name); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "Failed to open the directory name={}!", name); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_FAILED_OPEN_ENTITY); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Read(Kernel::HLERequestContext& ctx) { + auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry); + + LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + write_size = std::min<u64>(write_size, files.size()); + std::vector<DeliveryCacheDirectoryEntry> entries(write_size); + std::transform( + files.begin(), files.begin() + write_size, entries.begin(), [](const auto& file) { + FileName name{}; + std::memcpy(name.data(), file->GetName().data(), + std::min(file->GetName().size(), name.size())); + return DeliveryCacheDirectoryEntry{name, file->GetSize(), DigestFile(file)}; + }); + + ctx.WriteBuffer(entries); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(write_size * sizeof(DeliveryCacheDirectoryEntry)); + } + + void GetCount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + if (current_dir == nullptr) { + LOG_ERROR(Service_BCAT, "There is no open directory!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NO_OPEN_ENTITY); + return; + } + + const auto files = current_dir->GetFiles(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(files.size()); + } + + FileSys::VirtualDir root; + FileSys::VirtualDir current_dir; +}; + +class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> { +public: + IDeliveryCacheStorageService(FileSys::VirtualDir root_) + : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, + {1, &IDeliveryCacheStorageService::CreateDirectoryService, "CreateDirectoryService"}, + {10, &IDeliveryCacheStorageService::EnumerateDeliveryCacheDirectory, "EnumerateDeliveryCacheDirectory"}, + }; + // clang-format on + + RegisterHandlers(functions); + + for (const auto& subdir : root->GetSubdirectories()) { + DirectoryName name{}; + std::memcpy(name.data(), subdir->GetName().data(), + std::min(sizeof(DirectoryName) - 1, subdir->GetName().size())); + entries.push_back(name); + } + } + +private: + void CreateFileService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheFileService>(root); + } + + void CreateDirectoryService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root); + } + + void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { + auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName); + + LOG_DEBUG(Service_BCAT, "called, size={:016X}", size); + + size = std::min<u64>(size, entries.size() - next_read_index); + ctx.WriteBuffer(entries.data() + next_read_index, size * sizeof(DirectoryName)); + next_read_index += size; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(size); + } + + FileSys::VirtualDir root; + std::vector<DirectoryName> entries; + u64 next_read_index = 0; +}; + +void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_BCAT, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheStorageService>( + fsc.GetBCATDirectory(Core::CurrentProcess()->GetTitleID())); +} + +void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id)); +} + +std::unique_ptr<Backend> CreateBackendFromSettings(DirectoryGetter getter) { + const auto backend = Settings::values.bcat_backend; + +#ifdef YUZU_ENABLE_BOXCAT + if (backend == "boxcat") + return std::make_unique<Boxcat>(std::move(getter)); +#endif + + return std::make_unique<NullBackend>(std::move(getter)); } -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, + const char* name) + : ServiceFramework(name), module(std::move(module)), fsc(fsc), + backend(CreateBackendFromSettings([&fsc](u64 tid) { return fsc.GetBCATDirectory(tid); })) {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:m")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:u")->InstallAsService(service_manager); - std::make_shared<BCAT>(module, "bcat:s")->InstallAsService(service_manager); + std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:a") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:m") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:u") + ->InstallAsService(system.ServiceManager()); + std::make_shared<BCAT>(module, system.GetFileSystemController(), "bcat:s") + ->InstallAsService(system.ServiceManager()); } } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index f0d63cab0..27469926a 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -6,23 +6,39 @@ #include "core/hle/service/service.h" -namespace Service::BCAT { +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace BCAT { + +class Backend; class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, FileSystem::FileSystemController& fsc, + const char* name); ~Interface() override; void CreateBcatService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx); + void CreateDeliveryCacheStorageServiceWithApplicationId(Kernel::HLERequestContext& ctx); protected: + FileSystem::FileSystemController& fsc; + std::shared_ptr<Module> module; + std::unique_ptr<Backend> backend; }; }; /// Registers all BCAT services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); + +} // namespace BCAT -} // namespace Service::BCAT +} // namespace Service diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 14cd0e322..7fa4e820b 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -674,6 +674,15 @@ FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) return bis_factory->GetModificationDumpRoot(title_id); } +FileSys::VirtualDir 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; diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 3e0c03ec0..e6b49d8a2 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -110,6 +110,8 @@ public: FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; + FileSys::VirtualDir GetBCATDirectory(u64 title_id) const; + // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function // above is called. void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index f7a0aa4ff..a9cd119c4 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -165,12 +165,15 @@ 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(); - styleset_changed_event = Kernel::WritableEvent::CreateEventPair( - kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged"); + for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { + styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Manual, fmt::format("npad:NpadStyleSetChanged_{}", i)); + } if (!IsControllerActivated()) { return; @@ -431,7 +434,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) { @@ -450,10 +452,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_event.writable->Signal(); } } } @@ -468,7 +466,6 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { } void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { - styleset_changed_event.writable->Signal(); hold_type = joy_hold_type; } @@ -479,7 +476,9 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { 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()); - shared_memory_entries[npad_index].pad_assignment = assignment_mode; + if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { + shared_memory_entries[npad_index].pad_assignment = assignment_mode; + } } void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, @@ -498,11 +497,12 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, last_processed_vibration = vibrations.back(); } -Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent() const { +Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent( + u32 npad_id) const { // 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? - styleset_changed_event.writable->Signal(); - return styleset_changed_event.readable; + const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; + return styleset_event.readable; } Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index f72a9900c..1bc3d55d6 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -109,7 +109,7 @@ public: void VibrateController(const std::vector<u32>& controller_ids, const std::vector<Vibration>& vibrations); - Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent() const; + Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; void AddNewController(NPadControllerType controller); @@ -315,7 +315,8 @@ private: sticks; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; - Kernel::EventPair styleset_changed_event; + // Each controller should have their own styleset changed event + std::array<Kernel::EventPair, 10> styleset_changed_events; Vibration last_processed_vibration{}; std::array<ControllerHolder, 10> connected_controllers{}; bool can_controllers_vibrate{true}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 33145b891..8d76ba746 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -480,7 +480,7 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetStyleSetChangedEvent()); + .GetStyleSetChangedEvent(npad_id)); } void Hid::DisconnectNpad(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/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 5b8248433..1b52511a5 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/service.h" namespace Core { class System; @@ -38,8 +39,9 @@ public: * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) = 0; + virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) = 0; protected: Core::System& system; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 926a1285d..f764388bc 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -17,8 +17,9 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvdisp_disp0 ::~nvdisp_disp0() = default; -u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index e79e490ff..6fcdeee84 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -20,8 +20,9 @@ public: explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvdisp_disp0() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, 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 24ab3f2e9..6bc053f27 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -26,8 +26,9 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_as_gpu::~nvhost_as_gpu() = default; -u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 30ca5f4c3..169fb8f0e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -20,8 +20,9 @@ public: explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvhost_as_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index 9a66a5f88..ff6b1abae 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -19,8 +19,9 @@ nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface) : nvdevice(system), events_interface{events_interface} {} nvhost_ctrl::~nvhost_ctrl() = default; -u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 14e6e7e57..9898623de 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -17,8 +17,9 @@ public: explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface); ~nvhost_ctrl() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { 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 988effd90..389ace76f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -15,14 +15,15 @@ namespace Service::Nvidia::Devices { nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {} nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; -u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& input2, std::vector<u8>& output, + std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocGetCharacteristicsCommand: - return GetCharacteristics(input, output); + return GetCharacteristics(input, output, output2, version); case IoctlCommand::IocGetTPCMasksCommand: return GetTPCMasks(input, output); case IoctlCommand::IocGetActiveSlotMaskCommand: @@ -44,7 +45,8 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec return 0; } -u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& output2, IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -85,7 +87,13 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto params.gc.gr_compbit_store_base_hw = 0x0; params.gpu_characteristics_buf_size = 0xA0; params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) - std::memcpy(output.data(), ¶ms, output.size()); + + if (version == IoctlVersion::Version3) { + std::memcpy(output.data(), input.data(), output.size()); + std::memcpy(output2.data(), ¶ms.gc, output2.size()); + } else { + std::memcpy(output.data(), ¶ms, output.size()); + } return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index 2b035ae3f..642b0a2cb 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -16,8 +16,9 @@ public: explicit nvhost_ctrl_gpu(Core::System& system); ~nvhost_ctrl_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { @@ -162,7 +163,8 @@ private: }; static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size"); - u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& output2, IoctlVersion version); u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); u32 ZCullGetCtxSize(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 b4ee2a255..2b8d1bef6 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -17,8 +17,9 @@ nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_gpu::~nvhost_gpu() = default; -u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -50,7 +51,7 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u return SubmitGPFIFO(input, output); } if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) { - return KickoffPB(input, output); + return KickoffPB(input, output, input2, version); } } @@ -173,7 +174,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp return 0; } -u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, + const std::vector<u8>& input2, IoctlVersion version) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); } @@ -183,9 +185,13 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) params.num_entries, params.flags.raw); Tegra::CommandList entries(params.num_entries); - Memory::ReadBlock(params.address, entries.data(), - params.num_entries * sizeof(Tegra::CommandListHeader)); - + if (version == IoctlVersion::Version2) { + std::memcpy(entries.data(), input2.data(), + params.num_entries * sizeof(Tegra::CommandListHeader)); + } else { + Memory::ReadBlock(params.address, entries.data(), + params.num_entries * sizeof(Tegra::CommandListHeader)); + } UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index d2e8fbae9..d056dd046 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -24,8 +24,9 @@ public: explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvhost_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { @@ -183,7 +184,8 @@ private: u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); - u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output); + u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, + const std::vector<u8>& input2, IoctlVersion version); u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index f572ad30f..bdae8b887 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices { nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {} nvhost_nvdec::~nvhost_nvdec() = default; -u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index 2710f0511..cbdac8069 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -16,8 +16,9 @@ public: explicit nvhost_nvdec(Core::System& system); ~nvhost_nvdec() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index 38282956f..96e7b7dab 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices { nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} nvhost_nvjpg::~nvhost_nvjpg() = default; -u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 379766693..98dcac52f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -16,8 +16,9 @@ public: explicit nvhost_nvjpg(Core::System& system); ~nvhost_nvjpg() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index 70e8091db..c695b8863 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -13,8 +13,9 @@ namespace Service::Nvidia::Devices { nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {} nvhost_vic::~nvhost_vic() = default; -u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index 7d111977e..bec32bea1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -16,8 +16,9 @@ public: explicit nvhost_vic(Core::System& system); ~nvhost_vic() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 223b496b7..8c742316c 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -28,8 +28,9 @@ VAddr nvmap::GetObjectAddress(u32 handle) const { return object->addr; } -u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::Create: return IocCreate(input, output); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index bf4a101c2..73c2e8809 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -22,8 +22,9 @@ public: /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; /// Represents an nvmap object. struct Object { diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index d5be64ed2..5e0c23602 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -33,42 +33,77 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); } -void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - +void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) { IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); u32 command = rp.Pop<u32>(); - std::vector<u8> output(ctx.GetWriteBufferSize()); + /// Ioctl 3 has 2 outputs, first in the input params, second is the result + std::vector<u8> output(ctx.GetWriteBufferSize(0)); + std::vector<u8> output2; + if (version == IoctlVersion::Version3) { + output2.resize((ctx.GetWriteBufferSize(1))); + } + + /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer. + /// KickOfPB uses this + auto input = ctx.ReadBuffer(0); + + std::vector<u8> input2; + if (version == IoctlVersion::Version2) { + input2 = ctx.ReadBuffer(1); + } IoctlCtrl ctrl{}; - u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output, ctrl); + u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version); if (ctrl.must_delay) { ctrl.fresh_call = false; - ctx.SleepClientThread( - "NVServices::DelayedResponse", ctrl.timeout, - [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, - Kernel::ThreadWakeupReason reason) { - IoctlCtrl ctrl2{ctrl}; - std::vector<u8> output2 = output; - u32 result = nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output2, ctrl2); - ctx.WriteBuffer(output2); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(result); - }, - nvdrv->GetEventWriteable(ctrl.event_id)); + ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout, + [=](Kernel::SharedPtr<Kernel::Thread> thread, + Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + IoctlCtrl ctrl2{ctrl}; + std::vector<u8> tmp_output = output; + std::vector<u8> tmp_output2 = output2; + u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output, + tmp_output2, ctrl2, version); + ctx.WriteBuffer(tmp_output, 0); + if (version == IoctlVersion::Version3) { + ctx.WriteBuffer(tmp_output2, 1); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(result); + }, + nvdrv->GetEventWriteable(ctrl.event_id)); } else { ctx.WriteBuffer(output); + if (version == IoctlVersion::Version3) { + ctx.WriteBuffer(output2, 1); + } } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(result); } +void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version1); +} + +void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version2); +} + +void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version3); +} + void NVDRV::Close(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); @@ -154,8 +189,8 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) {8, &NVDRV::SetClientPID, "SetClientPID"}, {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, {10, nullptr, "InitializeDevtools"}, - {11, &NVDRV::Ioctl, "Ioctl2"}, - {12, nullptr, "Ioctl3"}, + {11, &NVDRV::Ioctl2, "Ioctl2"}, + {12, &NVDRV::Ioctl3, "Ioctl3"}, {13, &NVDRV::FinishInitialize, "FinishInitialize"}, }; RegisterHandlers(functions); diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 10a0ecd52..9269ce00c 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -24,6 +24,8 @@ public: private: void Open(Kernel::HLERequestContext& ctx); void Ioctl(Kernel::HLERequestContext& ctx); + void Ioctl2(Kernel::HLERequestContext& ctx); + void Ioctl3(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); void Initialize(Kernel::HLERequestContext& ctx); void QueryEvent(Kernel::HLERequestContext& ctx); @@ -31,6 +33,7 @@ private: void FinishInitialize(Kernel::HLERequestContext& ctx); void GetStatus(Kernel::HLERequestContext& ctx); void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); + void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version); std::shared_ptr<Module> nvdrv; diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h index ac03cbc23..529b03471 100644 --- a/src/core/hle/service/nvdrv/nvdata.h +++ b/src/core/hle/service/nvdrv/nvdata.h @@ -34,6 +34,12 @@ enum class EventState { Busy = 3, }; +enum class IoctlVersion : u32 { + Version1, + Version2, + Version3, +}; + struct IoctlCtrl { // First call done to the servioce for services that call itself again after a call. bool fresh_call{true}; diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 2011a226a..307a7e928 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -71,13 +71,14 @@ u32 Module::Open(const std::string& device_name) { return fd; } -u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl) { +u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { auto itr = open_files.find(fd); ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); auto& device = itr->second; - return device->ioctl({command}, input, output, ctrl); + return device->ioctl({command}, input, input2, output, output2, ctrl, version); } ResultCode Module::Close(u32 fd) { diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index a339ab672..f8bb28969 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -106,8 +106,9 @@ public: /// Opens a device node and returns a file descriptor to it. u32 Open(const std::string& device_name); /// Sends an ioctl command to the specified file descriptor. - u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output, - IoctlCtrl& ctrl); + u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version); /// Closes a device file descriptor and returns operation success. ResultCode Close(u32 fd); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 831a427de..f2c6fe9dc 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -208,7 +208,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { AOC::InstallInterfaces(*sm, system); APM::InstallInterfaces(system); Audio::InstallInterfaces(*sm, system); - BCAT::InstallInterfaces(*sm); + BCAT::InstallInterfaces(system); BPC::InstallInterfaces(*sm); BtDrv::InstallInterfaces(*sm, system); BTM::InstallInterfaces(*sm, system); 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/settings.cpp b/src/core/settings.cpp index 7de3fd1e5..d1fc94060 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -103,6 +103,8 @@ void LogSettings() { LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub); LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port); LogSetting("Debugging_ProgramArgs", Settings::values.program_args); + LogSetting("Services_BCATBackend", Settings::values.bcat_backend); + LogSetting("Services_BCATBoxcatLocal", Settings::values.bcat_boxcat_local); } } // namespace Settings diff --git a/src/core/settings.h b/src/core/settings.h index 47bddfb30..9c98a9287 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -448,6 +448,10 @@ struct Values { bool reporting_services; bool quest_flag; + // BCAT + std::string bcat_backend; + bool bcat_boxcat_local; + // WebService bool enable_telemetry; std::string web_api_url; diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 28272ef6f..7a6355ce2 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -544,7 +544,7 @@ enum class VoteOperation : u64 { Eq = 2, // allThreadsEqualNV }; -enum class ImageAtomicSize : u64 { +enum class ImageAtomicOperationType : u64 { U32 = 0, S32 = 1, U64 = 2, @@ -1432,11 +1432,11 @@ union Instruction { ASSERT(mode == SurfaceDataMode::D_BA); return store_data_layout; } - } sust; + } suldst; union { BitField<28, 1, u64> is_ba; - BitField<51, 3, ImageAtomicSize> size; + BitField<51, 3, ImageAtomicOperationType> operation_type; BitField<33, 3, ImageType> image_type; BitField<29, 4, ImageAtomicOperation> operation; BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; @@ -1595,6 +1595,7 @@ public: TMML_B, // Texture Mip Map Level TMML, // Texture Mip Map Level SUST, // Surface Store + SULD, // Surface Load SUATOM, // Surface Atomic Operation EXIT, NOP, @@ -1884,6 +1885,7 @@ private: INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), INST("11101011001-----", Id::SUST, Type::Image, "SUST"), + INST("11101011000-----", Id::SULD, Type::Image, "SULD"), INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"), INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 4f59a87b4..64de7e425 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> #include <cstddef> +#include <vector> #include <glad/glad.h> #include "common/logging/log.h" @@ -30,9 +32,27 @@ bool TestProgram(const GLchar* glsl) { return link_status == GL_TRUE; } +std::vector<std::string_view> GetExtensions() { + GLint num_extensions; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + std::vector<std::string_view> extensions; + extensions.reserve(num_extensions); + for (GLint index = 0; index < num_extensions; ++index) { + extensions.push_back( + reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index)))); + } + return extensions; +} + +bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) { + return std::find(images.begin(), images.end(), extension) != images.end(); +} + } // Anonymous namespace Device::Device() { + const std::vector extensions = GetExtensions(); + uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); @@ -40,6 +60,7 @@ Device::Device() { has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && GLAD_GL_NV_shader_thread_shuffle; has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; + has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted"); has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = TestComponentIndexingBug(); has_precise_bug = TestPreciseBug(); @@ -55,6 +76,7 @@ Device::Device(std::nullptr_t) { max_varyings = 15; has_warp_intrinsics = true; has_vertex_viewport_layer = true; + has_image_load_formatted = true; has_variable_aoffi = true; has_component_indexing_bug = false; has_precise_bug = false; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index ba6dcd3be..bb273c3d6 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -38,6 +38,10 @@ public: return has_vertex_viewport_layer; } + bool HasImageLoadFormatted() const { + return has_image_load_formatted; + } + bool HasVariableAoffi() const { return has_variable_aoffi; } @@ -61,6 +65,7 @@ private: u32 max_varyings{}; bool has_warp_intrinsics{}; bool has_vertex_viewport_layer{}; + bool has_image_load_formatted{}; bool has_variable_aoffi{}; bool has_component_indexing_bug{}; bool has_precise_bug{}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 0dbc4c02f..42ca3b1bd 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -211,14 +211,14 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn const auto primitive_mode{variant.primitive_mode}; const auto texture_buffer_usage{variant.texture_buffer_usage}; - std::string source = "#version 430 core\n" - "#extension GL_ARB_separate_shader_objects : enable\n" - "#extension GL_NV_gpu_shader5 : enable\n" - "#extension GL_NV_shader_thread_group : enable\n" - "#extension GL_NV_shader_thread_shuffle : enable\n"; - if (entries.shader_viewport_layer_array) { - source += "#extension GL_ARB_shader_viewport_layer_array : enable\n"; - } + std::string source = R"(#version 430 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shader_viewport_layer_array : enable +#extension GL_EXT_shader_image_load_formatted : enable +#extension GL_NV_gpu_shader5 : enable +#extension GL_NV_shader_thread_group : enable +#extension GL_NV_shader_thread_shuffle : enable +)"; if (program_type == ProgramType::Compute) { source += "#extension GL_ARB_compute_variable_group_size : require\n"; } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 74cb59bc1..8fa9e6534 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -19,6 +19,7 @@ #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" +#include "video_core/shader/node.h" #include "video_core/shader/shader_ir.h" namespace OpenGL::GLShader { @@ -241,6 +242,26 @@ constexpr const char* GetTypeString(Type type) { } } +constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) { + switch (image_type) { + case Tegra::Shader::ImageType::Texture1D: + return "1D"; + case Tegra::Shader::ImageType::TextureBuffer: + return "Buffer"; + case Tegra::Shader::ImageType::Texture1DArray: + return "1DArray"; + case Tegra::Shader::ImageType::Texture2D: + return "2D"; + case Tegra::Shader::ImageType::Texture2DArray: + return "2DArray"; + case Tegra::Shader::ImageType::Texture3D: + return "3D"; + default: + UNREACHABLE(); + return "1D"; + } +} + /// Generates code to use for a swizzle operation. constexpr const char* GetSwizzle(u32 element) { constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; @@ -398,8 +419,6 @@ public: usage.is_read, usage.is_written); } entries.clip_distances = ir.GetClipDistances(); - entries.shader_viewport_layer_array = - IsVertexShader(stage) && (ir.UsesLayer() || ir.UsesViewportIndex()); entries.shader_length = ir.GetLength(); return entries; } @@ -722,42 +741,6 @@ private: void DeclareImages() { const auto& images{ir.GetImages()}; for (const auto& [offset, image] : images) { - const char* image_type = [&] { - switch (image.GetType()) { - case Tegra::Shader::ImageType::Texture1D: - return "image1D"; - case Tegra::Shader::ImageType::TextureBuffer: - return "imageBuffer"; - case Tegra::Shader::ImageType::Texture1DArray: - return "image1DArray"; - case Tegra::Shader::ImageType::Texture2D: - return "image2D"; - case Tegra::Shader::ImageType::Texture2DArray: - return "image2DArray"; - case Tegra::Shader::ImageType::Texture3D: - return "image3D"; - default: - UNREACHABLE(); - return "image1D"; - } - }(); - - const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> { - if (!image.IsSizeKnown()) { - return {"", ""}; - } - switch (image.GetSize()) { - case Tegra::Shader::ImageAtomicSize::U32: - return {"u", "r32ui, "}; - case Tegra::Shader::ImageAtomicSize::S32: - return {"i", "r32i, "}; - default: - UNIMPLEMENTED_MSG("Unimplemented atomic size={}", - static_cast<u32>(image.GetSize())); - return {"", ""}; - } - }(); - std::string qualifier = "coherent volatile"; if (image.IsRead() && !image.IsWritten()) { qualifier += " readonly"; @@ -765,9 +748,10 @@ private: qualifier += " writeonly"; } - code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " - "{} {};", - image.GetIndex(), qualifier, image_type, GetImage(image)); + const char* format = image.IsAtomic() ? "r32ui, " : ""; + const char* type_declaration = GetImageTypeDeclaration(image.GetType()); + code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format, + image.GetIndex(), qualifier, type_declaration, GetImage(image)); } if (!images.empty()) { code.AddNewLine(); @@ -1234,28 +1218,13 @@ private: } std::string BuildImageValues(Operation operation) { + constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"}; const auto meta{std::get<MetaImage>(operation.GetMeta())}; - const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> { - constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"}; - if (!meta.image.IsSizeKnown()) { - return {float_constructors, Type::Float}; - } - switch (meta.image.GetSize()) { - case Tegra::Shader::ImageAtomicSize::U32: - return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint}; - case Tegra::Shader::ImageAtomicSize::S32: - return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint}; - default: - UNIMPLEMENTED_MSG("Unimplemented image size={}", - static_cast<u32>(meta.image.GetSize())); - return {float_constructors, Type::Float}; - } - }(); const std::size_t values_count{meta.values.size()}; std::string expr = fmt::format("{}(", constructors.at(values_count - 1)); for (std::size_t i = 0; i < values_count; ++i) { - expr += Visit(meta.values.at(i)).As(type); + expr += Visit(meta.values.at(i)).AsUint(); if (i + 1 < values_count) { expr += ", "; } @@ -1264,29 +1233,6 @@ private: return expr; } - Expression AtomicImage(Operation operation, const char* opname) { - constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; - const auto meta{std::get<MetaImage>(operation.GetMeta())}; - ASSERT(meta.values.size() == 1); - ASSERT(meta.image.IsSizeKnown()); - - const auto type = [&]() { - switch (const auto size = meta.image.GetSize()) { - case Tegra::Shader::ImageAtomicSize::U32: - return Type::Uint; - case Tegra::Shader::ImageAtomicSize::S32: - return Type::Int; - default: - UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size)); - return Type::Uint; - } - }(); - - return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image), - BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)), - type}; - } - Expression Assign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; @@ -1545,6 +1491,8 @@ private: case Tegra::Shader::HalfType::H1_H1: return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; } + UNREACHABLE(); + return {"0", Type::Int}; } Expression HMergeF32(Operation operation) { @@ -1809,6 +1757,19 @@ private: return {tmp, Type::Float}; } + Expression ImageLoad(Operation operation) { + if (!device.HasImageLoadFormatted()) { + LOG_ERROR(Render_OpenGL, + "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load"); + return {"0", Type::Int}; + } + + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image), + BuildIntegerCoordinates(operation), GetSwizzle(meta.element)), + Type::Uint}; + } + Expression ImageStore(Operation operation) { const auto meta{std::get<MetaImage>(operation.GetMeta())}; code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), @@ -1816,31 +1777,14 @@ private: return {}; } - Expression AtomicImageAdd(Operation operation) { - return AtomicImage(operation, "imageAtomicAdd"); - } - - Expression AtomicImageMin(Operation operation) { - return AtomicImage(operation, "imageAtomicMin"); - } - - Expression AtomicImageMax(Operation operation) { - return AtomicImage(operation, "imageAtomicMax"); - } - Expression AtomicImageAnd(Operation operation) { - return AtomicImage(operation, "imageAtomicAnd"); - } - - Expression AtomicImageOr(Operation operation) { - return AtomicImage(operation, "imageAtomicOr"); - } - - Expression AtomicImageXor(Operation operation) { - return AtomicImage(operation, "imageAtomicXor"); - } + template <const std::string_view& opname> + Expression AtomicImage(Operation operation) { + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + ASSERT(meta.values.size() == 1); - Expression AtomicImageExchange(Operation operation) { - return AtomicImage(operation, "imageAtomicExchange"); + return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image), + BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()), + Type::Uint}; } Expression Branch(Operation operation) { @@ -2035,6 +1979,12 @@ private: Func() = delete; ~Func() = delete; + static constexpr std::string_view Add = "Add"; + static constexpr std::string_view And = "And"; + static constexpr std::string_view Or = "Or"; + static constexpr std::string_view Xor = "Xor"; + static constexpr std::string_view Exchange = "Exchange"; + static constexpr std::string_view ShuffleIndexed = "shuffleNV"; static constexpr std::string_view ShuffleUp = "shuffleUpNV"; static constexpr std::string_view ShuffleDown = "shuffleDownNV"; @@ -2172,14 +2122,14 @@ private: &GLSLDecompiler::TextureQueryLod, &GLSLDecompiler::TexelFetch, + &GLSLDecompiler::ImageLoad, &GLSLDecompiler::ImageStore, - &GLSLDecompiler::AtomicImageAdd, - &GLSLDecompiler::AtomicImageMin, - &GLSLDecompiler::AtomicImageMax, - &GLSLDecompiler::AtomicImageAnd, - &GLSLDecompiler::AtomicImageOr, - &GLSLDecompiler::AtomicImageXor, - &GLSLDecompiler::AtomicImageExchange, + + &GLSLDecompiler::AtomicImage<Func::Add>, + &GLSLDecompiler::AtomicImage<Func::And>, + &GLSLDecompiler::AtomicImage<Func::Or>, + &GLSLDecompiler::AtomicImage<Func::Xor>, + &GLSLDecompiler::AtomicImage<Func::Exchange>, &GLSLDecompiler::Branch, &GLSLDecompiler::BranchIndirect, diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index 2ea02f5bf..e538dc001 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -90,7 +90,6 @@ struct ShaderEntries { std::vector<ImageEntry> images; std::vector<GlobalMemoryEntry> global_memory_entries; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; - bool shader_viewport_layer_array{}; std::size_t shader_length{}; }; diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index f141c4e3b..6a7012b54 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -343,20 +343,17 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn u8 is_bindless{}; u8 is_written{}; u8 is_read{}; - u8 is_size_known{}; - u32 size{}; + u8 is_atomic{}; if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || - !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) { + !LoadObjectFromPrecompiled(is_atomic)) { return {}; } entry.entries.images.emplace_back( static_cast<std::size_t>(offset), static_cast<std::size_t>(index), static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0, - is_read != 0, - is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size)) - : std::nullopt); + is_read != 0, is_atomic != 0); } u32 global_memory_count{}; @@ -382,12 +379,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn } } - bool shader_viewport_layer_array{}; - if (!LoadObjectFromPrecompiled(shader_viewport_layer_array)) { - return {}; - } - entry.entries.shader_viewport_layer_array = shader_viewport_layer_array; - u64 shader_length{}; if (!LoadObjectFromPrecompiled(shader_length)) { return {}; @@ -435,14 +426,13 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: return false; } for (const auto& image : entries.images) { - const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U; if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) || !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) || !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || - !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) { + !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) { return false; } } @@ -464,10 +454,6 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: } } - if (!SaveObjectToPrecompiled(entries.shader_viewport_layer_array)) { - return false; - } - if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) { return false; } diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index f7fbbb6e4..77fc58f25 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -19,6 +19,7 @@ #include "video_core/engines/shader_header.h" #include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" +#include "video_core/shader/node.h" #include "video_core/shader/shader_ir.h" namespace Vulkan::VKShader { @@ -939,22 +940,17 @@ private: return {}; } - Id ImageStore(Operation operation) { - UNIMPLEMENTED(); - return {}; - } - - Id AtomicImageAdd(Operation operation) { + Id ImageLoad(Operation operation) { UNIMPLEMENTED(); return {}; } - Id AtomicImageMin(Operation operation) { + Id ImageStore(Operation operation) { UNIMPLEMENTED(); return {}; } - Id AtomicImageMax(Operation operation) { + Id AtomicImageAdd(Operation operation) { UNIMPLEMENTED(); return {}; } @@ -1440,10 +1436,9 @@ private: &SPIRVDecompiler::TextureQueryLod, &SPIRVDecompiler::TexelFetch, + &SPIRVDecompiler::ImageLoad, &SPIRVDecompiler::ImageStore, &SPIRVDecompiler::AtomicImageAdd, - &SPIRVDecompiler::AtomicImageMin, - &SPIRVDecompiler::AtomicImageMax, &SPIRVDecompiler::AtomicImageAnd, &SPIRVDecompiler::AtomicImageOr, &SPIRVDecompiler::AtomicImageXor, diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index d54fb88c9..95ec1cdd9 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -41,11 +41,46 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); + const auto GetCoordinates = [this, instr](Tegra::Shader::ImageType image_type) { + std::vector<Node> coords; + const std::size_t num_coords{GetImageTypeNumCoordinates(image_type)}; + coords.reserve(num_coords); + for (std::size_t i = 0; i < num_coords; ++i) { + coords.push_back(GetRegister(instr.gpr8.Value() + i)); + } + return coords; + }; + switch (opcode->get().GetId()) { + case OpCode::Id::SULD: { + UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); + UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != + Tegra::Shader::OutOfBoundsStore::Ignore); + + const auto type{instr.suldst.image_type}; + auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + image.MarkRead(); + + u32 indexer = 0; + for (u32 element = 0; element < 4; ++element) { + if (!instr.suldst.IsComponentEnabled(element)) { + continue; + } + MetaImage meta{image, {}, element}; + Node value = Operation(OperationCode::ImageLoad, meta, GetCoordinates(type)); + SetTemporary(bb, indexer++, std::move(value)); + } + for (u32 i = 0; i < indexer; ++i) { + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); + } + break; + } case OpCode::Id::SUST: { - UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); - UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); - UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store + UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); + UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != + Tegra::Shader::OutOfBoundsStore::Ignore); + UNIMPLEMENTED_IF(instr.suldst.component_mask_selector != 0xf); // Ensure we have RGBA std::vector<Node> values; constexpr std::size_t hardcoded_size{4}; @@ -53,58 +88,51 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { values.push_back(GetRegister(instr.gpr0.Value() + i)); } - std::vector<Node> coords; - const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; - for (std::size_t i = 0; i < num_coords; ++i) { - coords.push_back(GetRegister(instr.gpr8.Value() + i)); - } - - const auto type{instr.sust.image_type}; - auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) - : GetBindlessImage(instr.gpr39, type)}; + const auto type{instr.suldst.image_type}; + auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; image.MarkWrite(); - MetaImage meta{image, values}; - bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords))); + MetaImage meta{image, std::move(values)}; + bb.push_back(Operation(OperationCode::ImageStore, meta, GetCoordinates(type))); break; } case OpCode::Id::SUATOM: { UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0); - Node value = GetRegister(instr.gpr0); - - std::vector<Node> coords; - const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; - for (std::size_t i = 0; i < num_coords; ++i) { - coords.push_back(GetRegister(instr.gpr8.Value() + i)); - } - const OperationCode operation_code = [instr] { - switch (instr.suatom_d.operation) { - case Tegra::Shader::ImageAtomicOperation::Add: - return OperationCode::AtomicImageAdd; - case Tegra::Shader::ImageAtomicOperation::Min: - return OperationCode::AtomicImageMin; - case Tegra::Shader::ImageAtomicOperation::Max: - return OperationCode::AtomicImageMax; - case Tegra::Shader::ImageAtomicOperation::And: - return OperationCode::AtomicImageAnd; - case Tegra::Shader::ImageAtomicOperation::Or: - return OperationCode::AtomicImageOr; - case Tegra::Shader::ImageAtomicOperation::Xor: - return OperationCode::AtomicImageXor; - case Tegra::Shader::ImageAtomicOperation::Exch: - return OperationCode::AtomicImageExchange; + switch (instr.suatom_d.operation_type) { + case Tegra::Shader::ImageAtomicOperationType::S32: + case Tegra::Shader::ImageAtomicOperationType::U32: + switch (instr.suatom_d.operation) { + case Tegra::Shader::ImageAtomicOperation::Add: + return OperationCode::AtomicImageAdd; + case Tegra::Shader::ImageAtomicOperation::And: + return OperationCode::AtomicImageAnd; + case Tegra::Shader::ImageAtomicOperation::Or: + return OperationCode::AtomicImageOr; + case Tegra::Shader::ImageAtomicOperation::Xor: + return OperationCode::AtomicImageXor; + case Tegra::Shader::ImageAtomicOperation::Exch: + return OperationCode::AtomicImageExchange; + } default: - UNIMPLEMENTED_MSG("Unimplemented operation={}", - static_cast<u32>(instr.suatom_d.operation.Value())); - return OperationCode::AtomicImageAdd; + break; } + UNIMPLEMENTED_MSG("Unimplemented operation={} type={}", + static_cast<u64>(instr.suatom_d.operation.Value()), + static_cast<u64>(instr.suatom_d.operation_type.Value())); + return OperationCode::AtomicImageAdd; }(); - const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)}; + Node value = GetRegister(instr.gpr0); + + const auto type = instr.suatom_d.image_type; + auto& image = GetImage(instr.image, type); + image.MarkAtomic(); + MetaImage meta{image, {std::move(value)}}; - SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords))); + SetRegister(bb, instr.gpr0, Operation(operation_code, meta, GetCoordinates(type))); break; } default: @@ -114,35 +142,32 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { return pc; } -Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { const auto offset{static_cast<std::size_t>(image.index.Value())}; - if (const auto image = TryUseExistingImage(offset, type, size)) { + if (const auto image = TryUseExistingImage(offset, type)) { return *image; } const std::size_t next_index{used_images.size()}; - return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second; + return used_images.emplace(offset, Image{offset, next_index, type}).first->second; } -Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { const Node image_register{GetRegister(reg)}; const auto [base_image, cbuf_index, cbuf_offset]{ TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; - if (const auto image = TryUseExistingImage(cbuf_key, type, size)) { + if (const auto image = TryUseExistingImage(cbuf_key, type)) { return *image; } const std::size_t next_index{used_images.size()}; - return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size}) + return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type}) .first->second; } -Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size) { +Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type) { auto it = used_images.find(offset); if (it == used_images.end()) { return nullptr; @@ -150,14 +175,6 @@ Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, auto& image = it->second; ASSERT(image.GetType() == type); - if (size) { - // We know the size, if it's known it has to be the same as before, otherwise we can set it. - if (image.IsSizeKnown()) { - ASSERT(image.GetSize() == size); - } else { - image.SetSize(*size); - } - } return ℑ } diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index abf2cb1ab..338bab17c 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -149,10 +149,10 @@ enum class OperationCode { TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 TexelFetch, /// (MetaTexture, int[N], int) -> float4 - ImageStore, /// (MetaImage, int[N] values) -> void + ImageLoad, /// (MetaImage, int[N] coords) -> void + ImageStore, /// (MetaImage, int[N] coords) -> void + AtomicImageAdd, /// (MetaImage, int[N] coords) -> void - AtomicImageMin, /// (MetaImage, int[N] coords) -> void - AtomicImageMax, /// (MetaImage, int[N] coords) -> void AtomicImageAnd, /// (MetaImage, int[N] coords) -> void AtomicImageOr, /// (MetaImage, int[N] coords) -> void AtomicImageXor, /// (MetaImage, int[N] coords) -> void @@ -294,21 +294,18 @@ private: class Image final { public: - constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size) - : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {} + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) + : offset{offset}, index{index}, type{type}, is_bindless{false} {} constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, - Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size) + Tegra::Shader::ImageType type) : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, - is_bindless{true}, size{size} {} + is_bindless{true} {} constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, - bool is_bindless, bool is_written, bool is_read, - std::optional<Tegra::Shader::ImageAtomicSize> size) + bool is_bindless, bool is_written, bool is_read, bool is_atomic) : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, - is_written{is_written}, is_read{is_read}, size{size} {} + is_written{is_written}, is_read{is_read}, is_atomic{is_atomic} {} void MarkWrite() { is_written = true; @@ -318,8 +315,10 @@ public: is_read = true; } - void SetSize(Tegra::Shader::ImageAtomicSize size_) { - size = size_; + void MarkAtomic() { + MarkWrite(); + MarkRead(); + is_atomic = true; } constexpr std::size_t GetOffset() const { @@ -346,21 +345,17 @@ public: return is_read; } - constexpr std::pair<u32, u32> GetBindlessCBuf() const { - return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; + constexpr bool IsAtomic() const { + return is_atomic; } - constexpr bool IsSizeKnown() const { - return size.has_value(); - } - - constexpr Tegra::Shader::ImageAtomicSize GetSize() const { - return size.value(); + constexpr std::pair<u32, u32> GetBindlessCBuf() const { + return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; } constexpr bool operator<(const Image& rhs) const { - return std::tie(offset, index, type, size, is_bindless) < - std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless); + return std::tie(offset, index, type, is_bindless) < + std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); } private: @@ -370,7 +365,7 @@ private: bool is_bindless{}; bool is_written{}; bool is_read{}; - std::optional<Tegra::Shader::ImageAtomicSize> size{}; + bool is_atomic{}; }; struct GlobalMemoryBase { @@ -402,6 +397,7 @@ struct MetaTexture { struct MetaImage { const Image& image; std::vector<Node> values; + u32 element{}; }; /// Parameters that modify an operation but are not part of any particular operand diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index 2f03d83ba..6f666ee30 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -284,16 +284,13 @@ private: bool is_shadow); /// Accesses an image. - Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size = {}); + Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); /// Access a bindless image sampler. - Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size = {}); + Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); /// Tries to access an existing image, updating it's state as needed - Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type, - std::optional<Tegra::Shader::ImageAtomicSize> size); + Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type); /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index dc6fa07fc..ff1c1d985 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -66,6 +66,9 @@ add_executable(yuzu configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui + configuration/configure_service.cpp + configuration/configure_service.h + configuration/configure_service.ui configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui @@ -186,6 +189,10 @@ if (YUZU_USE_QT_WEB_ENGINE) target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) endif () +if (YUZU_ENABLE_BOXCAT) + target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT) +endif () + if(UNIX AND NOT APPLE) install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") endif() diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 92d9fb161..4cb27ddb2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -525,6 +525,17 @@ void Config::ReadDebuggingValues() { qt_config->endGroup(); } +void Config::ReadServiceValues() { + qt_config->beginGroup(QStringLiteral("Services")); + Settings::values.bcat_backend = + ReadSetting(QStringLiteral("bcat_backend"), QStringLiteral("boxcat")) + .toString() + .toStdString(); + Settings::values.bcat_boxcat_local = + ReadSetting(QStringLiteral("bcat_boxcat_local"), false).toBool(); + qt_config->endGroup(); +} + void Config::ReadDisabledAddOnValues() { const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns")); @@ -769,6 +780,7 @@ void Config::ReadValues() { ReadMiscellaneousValues(); ReadDebuggingValues(); ReadWebServiceValues(); + ReadServiceValues(); ReadDisabledAddOnValues(); ReadUIValues(); } @@ -866,6 +878,7 @@ void Config::SaveValues() { SaveMiscellaneousValues(); SaveDebuggingValues(); SaveWebServiceValues(); + SaveServiceValues(); SaveDisabledAddOnValues(); SaveUIValues(); } @@ -963,6 +976,14 @@ void Config::SaveDebuggingValues() { qt_config->endGroup(); } +void Config::SaveServiceValues() { + qt_config->beginGroup(QStringLiteral("Services")); + WriteSetting(QStringLiteral("bcat_backend"), + QString::fromStdString(Settings::values.bcat_backend), QStringLiteral("null")); + WriteSetting(QStringLiteral("bcat_boxcat_local"), Settings::values.bcat_boxcat_local, false); + qt_config->endGroup(); +} + void Config::SaveDisabledAddOnValues() { qt_config->beginWriteArray(QStringLiteral("DisabledAddOns")); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 6b523ecdd..ba6888004 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,6 +42,7 @@ private: void ReadCoreValues(); void ReadDataStorageValues(); void ReadDebuggingValues(); + void ReadServiceValues(); void ReadDisabledAddOnValues(); void ReadMiscellaneousValues(); void ReadPathValues(); @@ -65,6 +66,7 @@ private: void SaveCoreValues(); void SaveDataStorageValues(); void SaveDebuggingValues(); + void SaveServiceValues(); void SaveDisabledAddOnValues(); void SaveMiscellaneousValues(); void SavePathValues(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 49fadd0ef..372427ae2 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -98,6 +98,11 @@ <string>Web</string> </attribute> </widget> + <widget class="ConfigureService" name="serviceTab"> + <attribute name="title"> + <string>Services</string> + </attribute> + </widget> </widget> </item> </layout> @@ -178,6 +183,12 @@ <header>configuration/configure_hotkeys.h</header> <container>1</container> </customwidget> + <customwidget> + <class>ConfigureService</class> + <extends>QWidget</extends> + <header>configuration/configure_service.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 7c875ae87..25b2e1b05 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -44,6 +44,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->audioTab->ApplyConfiguration(); ui->debugTab->ApplyConfiguration(); ui->webTab->ApplyConfiguration(); + ui->serviceTab->ApplyConfiguration(); Settings::Apply(); Settings::LogSettings(); } @@ -74,7 +75,8 @@ Q_DECLARE_METATYPE(QList<QWidget*>); void ConfigureDialog::PopulateSelectionList() { const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, - {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}}, + {tr("System"), + {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab, ui->audioTab}}, {tr("Graphics"), {ui->graphicsTab}}, {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, }; @@ -108,6 +110,7 @@ void ConfigureDialog::UpdateVisibleTabs() { {ui->webTab, tr("Web")}, {ui->gameListTab, tr("Game List")}, {ui->filesystemTab, tr("Filesystem")}, + {ui->serviceTab, tr("Services")}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index b49446be9..98bc9b391 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QCheckBox> #include <QSpinBox> #include "core/core.h" #include "core/settings.h" diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp new file mode 100644 index 000000000..81c9e933f --- /dev/null +++ b/src/yuzu/configuration/configure_service.cpp @@ -0,0 +1,136 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QGraphicsItem> +#include <QtConcurrent/QtConcurrent> +#include "core/hle/service/bcat/backend/boxcat.h" +#include "core/settings.h" +#include "ui_configure_service.h" +#include "yuzu/configuration/configure_service.h" + +namespace { +QString FormatEventStatusString(const Service::BCAT::EventStatus& status) { + QString out; + + if (status.header.has_value()) { + out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header)); + } + + if (status.events.size() == 1) { + out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front())); + } else { + for (const auto& event : status.events) { + out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event)); + } + } + + if (status.footer.has_value()) { + out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer)); + } + + return out; +} +} // Anonymous namespace + +ConfigureService::ConfigureService(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) { + ui->setupUi(this); + + ui->bcat_source->addItem(QStringLiteral("None")); + ui->bcat_empty_label->setHidden(true); + ui->bcat_empty_header->setHidden(true); + +#ifdef YUZU_ENABLE_BOXCAT + ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat")); +#endif + + connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureService::OnBCATImplChanged); + + this->SetConfiguration(); +} + +ConfigureService::~ConfigureService() = default; + +void ConfigureService::ApplyConfiguration() { + Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString(); +} + +void ConfigureService::RetranslateUi() { + ui->retranslateUi(this); +} + +void ConfigureService::SetConfiguration() { + const int index = + ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend)); + ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index); +} + +std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { + std::optional<std::string> global; + std::map<std::string, Service::BCAT::EventStatus> map; + const auto res = Service::BCAT::Boxcat::GetStatus(global, map); + + switch (res) { + case Service::BCAT::Boxcat::StatusResult::Offline: + return {QString{}, + tr("The boxcat service is offline or you are not connected to the internet.")}; + case Service::BCAT::Boxcat::StatusResult::ParseError: + return {QString{}, + tr("There was an error while processing the boxcat event data. Contact the yuzu " + "developers.")}; + case Service::BCAT::Boxcat::StatusResult::BadClientVersion: + return {QString{}, + tr("The version of yuzu you are using is either too new or too old for the server. " + "Try updating to the latest official release of yuzu.")}; + } + + if (map.empty()) { + return {QStringLiteral("Current Boxcat Events"), + tr("There are currently no events on boxcat.")}; + } + + QString out; + + if (global.has_value()) { + out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global)); + } + + for (const auto& [key, value] : map) { + out += QStringLiteral("%1<b>%2</b><br>%3") + .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>")) + .arg(QString::fromStdString(key)) + .arg(FormatEventStatusString(value)); + } + return {QStringLiteral("Current Boxcat Events"), std::move(out)}; +} + +void ConfigureService::OnBCATImplChanged() { +#ifdef YUZU_ENABLE_BOXCAT + const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); + ui->bcat_empty_header->setHidden(!boxcat); + ui->bcat_empty_label->setHidden(!boxcat); + ui->bcat_empty_header->setText(QString{}); + ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status...")); + + if (!boxcat) + return; + + const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); }); + + watcher.setFuture(future); + connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this, + [this] { OnUpdateBCATEmptyLabel(watcher.result()); }); +#endif +} + +void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) { +#ifdef YUZU_ENABLE_BOXCAT + const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); + if (boxcat) { + ui->bcat_empty_header->setText(string.first); + ui->bcat_empty_label->setText(string.second); + } +#endif +} diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_service.h new file mode 100644 index 000000000..f5c1b703a --- /dev/null +++ b/src/yuzu/configuration/configure_service.h @@ -0,0 +1,34 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QFutureWatcher> +#include <QWidget> + +namespace Ui { +class ConfigureService; +} + +class ConfigureService : public QWidget { + Q_OBJECT + +public: + explicit ConfigureService(QWidget* parent = nullptr); + ~ConfigureService() override; + + void ApplyConfiguration(); + void RetranslateUi(); + +private: + void SetConfiguration(); + + std::pair<QString, QString> BCATDownloadEvents(); + void OnBCATImplChanged(); + void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string); + + std::unique_ptr<Ui::ConfigureService> ui; + QFutureWatcher<std::pair<QString, QString>> watcher{this}; +}; diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_service.ui new file mode 100644 index 000000000..9668dd557 --- /dev/null +++ b/src/yuzu/configuration/configure_service.ui @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureService</class> + <widget class="QWidget" name="ConfigureService"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>433</width> + <height>561</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>BCAT</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1" colspan="2"> + <widget class="QLabel" name="label_2"> + <property name="maximumSize"> + <size> + <width>260</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>BCAT Backend</string> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="QLabel" name="bcat_empty_label"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="maximumSize"> + <size> + <width>260</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html></string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QComboBox" name="bcat_source"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="bcat_empty_header"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f147044d9..2d82df739 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -22,6 +22,8 @@ #include "core/frontend/applets/general_frontend.h" #include "core/frontend/scope_acquire_window_context.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/hid.h" @@ -83,6 +85,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/submission_package.h" #include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/sm/sm.h" @@ -1674,6 +1677,11 @@ void GMainWindow::OnStartGame() { } void GMainWindow::OnPauseGame() { + Core::System& system{Core::System::GetInstance()}; + if (system.GetExitLock() && !ConfirmForceLockedExit()) { + return; + } + emu_thread->SetRunning(false); ui.action_Start->setEnabled(true); @@ -1685,6 +1693,11 @@ void GMainWindow::OnPauseGame() { } void GMainWindow::OnStopGame() { + Core::System& system{Core::System::GetInstance()}; + if (system.GetExitLock() && !ConfirmForceLockedExit()) { + return; + } + ShutdownGame(); } @@ -2182,13 +2195,41 @@ bool GMainWindow::ConfirmChangeGame() { if (emu_thread == nullptr) return true; - auto answer = QMessageBox::question( + const auto answer = QMessageBox::question( this, tr("yuzu"), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); return answer != QMessageBox::No; } +bool GMainWindow::ConfirmForceLockedExit() { + if (emu_thread == nullptr) + return true; + + const auto answer = + QMessageBox::question(this, tr("yuzu"), + tr("The currently running application has requested yuzu to not " + "exit.\n\nWould you like to bypass this and exit anyway?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + return answer != QMessageBox::No; +} + +void GMainWindow::RequestGameExit() { + auto& sm{Core::System::GetInstance().ServiceManager()}; + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->RequestExit(); + has_signalled = true; + } + + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->RequestExit(); + } +} + void GMainWindow::filterBarSetChecked(bool state) { ui.action_Show_Filter_Bar->setChecked(state); emit(OnToggleFilterBar()); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7d16188cb..e942d1248 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -172,6 +172,8 @@ private: */ bool ConfirmClose(); bool ConfirmChangeGame(); + bool ConfirmForceLockedExit(); + void RequestGameExit(); void closeEvent(QCloseEvent* event) override; private slots: diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index d82438502..1a812cb87 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -433,6 +433,11 @@ void Config::ReadValues() { sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); + + // Services + Settings::values.bcat_backend = sdl2_config->Get("Services", "bcat_backend", "boxcat"); + Settings::values.bcat_boxcat_local = + sdl2_config->GetBoolean("Services", "bcat_boxcat_local", false); } void Config::Reload() { diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index a6171c3ed..8d18a4a5a 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -251,6 +251,11 @@ web_api_url = https://api.yuzu-emu.org yuzu_username = yuzu_token = +[Services] +# The name of the backend to use for BCAT +# If this is set to 'boxcat' boxcat will be used, otherwise a null implementation will be used +bcat_backend = + [AddOns] # Used to disable add-ons # List of title IDs of games that will have add-ons disabled (separated by '|'): |