diff --git a/README.md b/README.md index 72587fb6c..18f973c4c 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 4161. +This is the source code for early-access 4162. ## Legal Notice diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fc07115ed..6a55ec03b 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -486,8 +486,10 @@ add_library(core STATIC hle/service/am/service/system_applet_proxy.h hle/service/am/service/window_controller.cpp hle/service/am/service/window_controller.h - hle/service/aoc/aoc_u.cpp - hle/service/aoc/aoc_u.h + hle/service/aoc/addon_content_manager.cpp + hle/service/aoc/addon_content_manager.h + hle/service/aoc/purchase_event_manager.cpp + hle/service/aoc/purchase_event_manager.h hle/service/apm/apm.cpp hle/service/apm/apm.h hle/service/apm/apm_controller.cpp diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp new file mode 100755 index 000000000..d47f57d64 --- /dev/null +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/file_sys/common_funcs.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/aoc/addon_content_manager.h" +#include "core/hle/service/aoc/purchase_event_manager.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/server_manager.h" +#include "core/loader/loader.h" + +namespace Service::AOC { + +static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { + return FileSys::GetBaseTitleID(title_id) == base; +} + +static std::vector AccumulateAOCTitleIDs(Core::System& system) { + std::vector add_on_content; + const auto& rcu = system.GetContentProvider(); + const auto list = + rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + std::transform(list.begin(), list.end(), std::back_inserter(add_on_content), + [](const FileSys::ContentProviderEntry& rce) { return rce.title_id; }); + add_on_content.erase( + std::remove_if( + add_on_content.begin(), add_on_content.end(), + [&rcu](u64 tid) { + return rcu.GetEntry(tid, FileSys::ContentRecordType::Data)->GetStatus() != + Loader::ResultStatus::Success; + }), + add_on_content.end()); + return add_on_content; +} + +IAddOnContentManager::IAddOnContentManager(Core::System& system_) + : ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)}, + service_context{system_, "aoc:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CountAddOnContentByApplicationId"}, + {1, nullptr, "ListAddOnContentByApplicationId"}, + {2, D<&IAddOnContentManager::CountAddOnContent>, "CountAddOnContent"}, + {3, D<&IAddOnContentManager::ListAddOnContent>, "ListAddOnContent"}, + {4, nullptr, "GetAddOnContentBaseIdByApplicationId"}, + {5, D<&IAddOnContentManager::GetAddOnContentBaseId>, "GetAddOnContentBaseId"}, + {6, nullptr, "PrepareAddOnContentByApplicationId"}, + {7, D<&IAddOnContentManager::PrepareAddOnContent>, "PrepareAddOnContent"}, + {8, D<&IAddOnContentManager::GetAddOnContentListChangedEvent>, "GetAddOnContentListChangedEvent"}, + {9, nullptr, "GetAddOnContentLostErrorCode"}, + {10, D<&IAddOnContentManager::GetAddOnContentListChangedEventWithProcessId>, "GetAddOnContentListChangedEventWithProcessId"}, + {11, D<&IAddOnContentManager::NotifyMountAddOnContent>, "NotifyMountAddOnContent"}, + {12, D<&IAddOnContentManager::NotifyUnmountAddOnContent>, "NotifyUnmountAddOnContent"}, + {13, nullptr, "IsAddOnContentMountedForDebug"}, + {50, D<&IAddOnContentManager::CheckAddOnContentMountStatus>, "CheckAddOnContentMountStatus"}, + {100, D<&IAddOnContentManager::CreateEcPurchasedEventManager>, "CreateEcPurchasedEventManager"}, + {101, D<&IAddOnContentManager::CreatePermanentEcPurchasedEventManager>, "CreatePermanentEcPurchasedEventManager"}, + {110, nullptr, "CreateContentsServiceManager"}, + {200, nullptr, "SetRequiredAddOnContentsOnContentsAvailabilityTransition"}, + {300, nullptr, "SetupHostAddOnContent"}, + {301, nullptr, "GetRegisteredAddOnContentPath"}, + {302, nullptr, "UpdateCachedList"}, + }; + // clang-format on + + RegisterHandlers(functions); + + aoc_change_event = service_context.CreateEvent("GetAddOnContentListChanged:Event"); +} + +IAddOnContentManager::~IAddOnContentManager() { + service_context.CloseEvent(aoc_change_event); +} + +Result IAddOnContentManager::CountAddOnContent(Out out_count, ClientProcessId process_id) { + LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); + + const auto current = system.GetApplicationProcessProgramID(); + + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { + *out_count = 0; + R_SUCCEED(); + } + + *out_count = static_cast( + std::count_if(add_on_content.begin(), add_on_content.end(), + [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })); + + R_SUCCEED(); +} + +Result IAddOnContentManager::ListAddOnContent(Out out_count, + OutBuffer out_addons, + u32 offset, u32 count, ClientProcessId process_id) { + LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count, + process_id.pid); + + const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID()); + + std::vector out; + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) { + for (u64 content_id : add_on_content) { + if (FileSys::GetBaseTitleID(content_id) != current) { + continue; + } + + out.push_back(static_cast(FileSys::GetAOCID(content_id))); + } + } + + // TODO(DarkLordZach): Find the correct error code. + R_UNLESS(out.size() >= offset, ResultUnknown); + + *out_count = static_cast(std::min(out.size() - offset, count)); + std::rotate(out.begin(), out.begin() + offset, out.end()); + + std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32)); + + R_SUCCEED(); +} + +Result IAddOnContentManager::GetAddOnContentBaseId(Out out_title_id, + ClientProcessId process_id) { + LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); + + const auto title_id = system.GetApplicationProcessProgramID(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; + + const auto res = pm.GetControlMetadata(); + if (res.first == nullptr) { + *out_title_id = FileSys::GetAOCBaseTitleID(title_id); + R_SUCCEED(); + } + + *out_title_id = res.first->GetDLCBaseTitleId(); + + R_SUCCEED(); +} + +Result IAddOnContentManager::PrepareAddOnContent(s32 addon_index, ClientProcessId process_id) { + LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index, + process_id.pid); + + R_SUCCEED(); +} + +Result IAddOnContentManager::GetAddOnContentListChangedEvent( + OutCopyHandle out_event) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + *out_event = &aoc_change_event->GetReadableEvent(); + + R_SUCCEED(); +} + +Result IAddOnContentManager::GetAddOnContentListChangedEventWithProcessId( + OutCopyHandle out_event, ClientProcessId process_id) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + *out_event = &aoc_change_event->GetReadableEvent(); + + R_SUCCEED(); +} + +Result IAddOnContentManager::NotifyMountAddOnContent() { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + R_SUCCEED(); +} + +Result IAddOnContentManager::NotifyUnmountAddOnContent() { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + R_SUCCEED(); +} + +Result IAddOnContentManager::CheckAddOnContentMountStatus() { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + R_SUCCEED(); +} + +Result IAddOnContentManager::CreateEcPurchasedEventManager( + OutInterface out_interface) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + *out_interface = std::make_shared(system); + + R_SUCCEED(); +} + +Result IAddOnContentManager::CreatePermanentEcPurchasedEventManager( + OutInterface out_interface) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + *out_interface = std::make_shared(system); + + R_SUCCEED(); +} + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("aoc:u", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::AOC diff --git a/src/core/hle/service/aoc/addon_content_manager.h b/src/core/hle/service/aoc/addon_content_manager.h new file mode 100755 index 000000000..91857df4c --- /dev/null +++ b/src/core/hle/service/aoc/addon_content_manager.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Kernel { +class KEvent; +} + +namespace Service::AOC { + +class IPurchaseEventManager; + +class IAddOnContentManager final : public ServiceFramework { +public: + explicit IAddOnContentManager(Core::System& system); + ~IAddOnContentManager() override; + + Result CountAddOnContent(Out out_count, ClientProcessId process_id); + Result ListAddOnContent(Out out_count, OutBuffer out_addons, + u32 offset, u32 count, ClientProcessId process_id); + Result GetAddOnContentBaseId(Out out_title_id, ClientProcessId process_id); + Result PrepareAddOnContent(s32 addon_index, ClientProcessId process_id); + Result GetAddOnContentListChangedEvent(OutCopyHandle out_event); + Result GetAddOnContentListChangedEventWithProcessId( + OutCopyHandle out_event, ClientProcessId process_id); + Result NotifyMountAddOnContent(); + Result NotifyUnmountAddOnContent(); + Result CheckAddOnContentMountStatus(); + Result CreateEcPurchasedEventManager(OutInterface out_interface); + Result CreatePermanentEcPurchasedEventManager( + OutInterface out_interface); + +private: + std::vector add_on_content; + KernelHelpers::ServiceContext service_context; + + Kernel::KEvent* aoc_change_event; +}; + +void LoopProcess(Core::System& system); + +} // namespace Service::AOC diff --git a/src/core/hle/service/aoc/purchase_event_manager.cpp b/src/core/hle/service/aoc/purchase_event_manager.cpp new file mode 100755 index 000000000..9e718510b --- /dev/null +++ b/src/core/hle/service/aoc/purchase_event_manager.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/aoc/purchase_event_manager.h" +#include "core/hle/service/cmif_serialization.h" + +namespace Service::AOC { + +constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400}; + +IPurchaseEventManager::IPurchaseEventManager(Core::System& system_) + : ServiceFramework{system_, "IPurchaseEventManager"}, service_context{system, + "IPurchaseEventManager"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, D<&IPurchaseEventManager::SetDefaultDeliveryTarget>, "SetDefaultDeliveryTarget"}, + {1, D<&IPurchaseEventManager::SetDeliveryTarget>, "SetDeliveryTarget"}, + {2, D<&IPurchaseEventManager::GetPurchasedEvent>, "GetPurchasedEvent"}, + {3, D<&IPurchaseEventManager::PopPurchasedProductInfo>, "PopPurchasedProductInfo"}, + {4, D<&IPurchaseEventManager::PopPurchasedProductInfoWithUid>, "PopPurchasedProductInfoWithUid"}, + }; + // clang-format on + + RegisterHandlers(functions); + + purchased_event = service_context.CreateEvent("IPurchaseEventManager:PurchasedEvent"); +} + +IPurchaseEventManager::~IPurchaseEventManager() { + service_context.CloseEvent(purchased_event); +} + +Result IPurchaseEventManager::SetDefaultDeliveryTarget( + ClientProcessId process_id, InBuffer in_buffer) { + LOG_WARNING(Service_AOC, "(STUBBED) called, process_id={}", process_id.pid); + + R_SUCCEED(); +} + +Result IPurchaseEventManager::SetDeliveryTarget(u64 unknown, + InBuffer in_buffer) { + LOG_WARNING(Service_AOC, "(STUBBED) called, unknown={}", unknown); + + R_SUCCEED(); +} + +Result IPurchaseEventManager::GetPurchasedEvent(OutCopyHandle out_event) { + LOG_WARNING(Service_AOC, "called"); + + *out_event = &purchased_event->GetReadableEvent(); + + R_SUCCEED(); +} + +Result IPurchaseEventManager::PopPurchasedProductInfo() { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + R_RETURN(ResultNoPurchasedProductInfoAvailable); +} + +Result IPurchaseEventManager::PopPurchasedProductInfoWithUid() { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + R_RETURN(ResultNoPurchasedProductInfoAvailable); +} + +} // namespace Service::AOC diff --git a/src/core/hle/service/aoc/purchase_event_manager.h b/src/core/hle/service/aoc/purchase_event_manager.h new file mode 100755 index 000000000..ea3836bc9 --- /dev/null +++ b/src/core/hle/service/aoc/purchase_event_manager.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/os/event.h" +#include "core/hle/service/service.h" + +namespace Service::AOC { + +class IPurchaseEventManager final : public ServiceFramework { +public: + explicit IPurchaseEventManager(Core::System& system_); + ~IPurchaseEventManager() override; + + Result SetDefaultDeliveryTarget(ClientProcessId process_id, + InBuffer in_buffer); + Result SetDeliveryTarget(u64 unknown, InBuffer in_buffer); + Result GetPurchasedEvent(OutCopyHandle out_event); + Result PopPurchasedProductInfo(); + Result PopPurchasedProductInfoWithUid(); + +private: + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* purchased_event; +}; + +} // namespace Service::AOC diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 1aa85ea54..3defa4b31 100755 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -5,7 +5,7 @@ #include "core/hle/service/acc/acc.h" #include "core/hle/service/am/am.h" -#include "core/hle/service/aoc/aoc_u.h" +#include "core/hle/service/aoc/addon_content_manager.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/audio/audio.h" #include "core/hle/service/bcat/bcat.h" diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ed820399b..6c8540186 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3010,9 +3010,6 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, GameListShortcutTarget target) { - std::string game_title; - QString qt_game_title; - std::filesystem::path out_icon_path; // Get path to yuzu executable const QStringList args = QApplication::arguments(); std::filesystem::path yuzu_command = args[0].toStdString(); @@ -3029,48 +3026,51 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString(); } - // Icon path and title - if (std::filesystem::exists(shortcut_path)) { - // Get title from game file - const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), - system->GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - const auto loader = - Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); - game_title = fmt::format("{:016X}", program_id); - if (control.first != nullptr) { - game_title = control.first->GetApplicationName(); - } else { - loader->ReadTitle(game_title); - } - // Delete illegal characters from title - const std::string illegal_chars = "<>:\"/\\|?*."; - for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { - if (illegal_chars.find(*it) != std::string::npos) { - game_title.erase(it.base() - 1); - } - } - qt_game_title = QString::fromStdString(game_title); - // Get icon from game file - std::vector icon_image_file{}; - if (control.second != nullptr) { - icon_image_file = control.second->ReadAllBytes(); - } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { - LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); - } - QImage icon_data = - QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); - if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { - if (!SaveIconToFile(out_icon_path, icon_data)) { - LOG_ERROR(Frontend, "Could not write icon to file"); - } - } - } else { - GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, - qt_game_title); - LOG_ERROR(Frontend, "Invalid shortcut target"); + + if (!std::filesystem::exists(shortcut_path)) { + GMainWindow::CreateShortcutMessagesGUI( + this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, + QString::fromStdString(shortcut_path.generic_string())); + LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string()); return; } + + // Get title from game file + const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), + system->GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + const auto loader = + Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); + std::string game_title = fmt::format("{:016X}", program_id); + if (control.first != nullptr) { + game_title = control.first->GetApplicationName(); + } else { + loader->ReadTitle(game_title); + } + // Delete illegal characters from title + const std::string illegal_chars = "<>:\"/\\|?*."; + for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { + if (illegal_chars.find(*it) != std::string::npos) { + game_title.erase(it.base() - 1); + } + } + const QString qt_game_title = QString::fromStdString(game_title); + // Get icon from game file + std::vector icon_image_file{}; + if (control.second != nullptr) { + icon_image_file = control.second->ReadAllBytes(); + } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { + LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); + } + QImage icon_data = + QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); + std::filesystem::path out_icon_path; + if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { + if (!SaveIconToFile(out_icon_path, icon_data)) { + LOG_ERROR(Frontend, "Could not write icon to file"); + } + } + #if defined(__linux__) // Special case for AppImages // Warn once if we are making a shortcut to a volatile AppImage