From 30cbec7ad5de5cde2f63f669139ea06e9e925aa7 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Mon, 19 Jan 2026 04:18:55 +0100 Subject: [PATCH] [core/hle] Adds sorting game titles and prevents hard crashes in qlaunch (#3330) Adds correct sorting in qlaunch (tested with FW 19 and FW20-21) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3330 Reviewed-by: DraVee Reviewed-by: CamilleLaVey Reviewed-by: MaranBr Co-authored-by: Maufeat Co-committed-by: Maufeat --- src/core/file_sys/control_metadata.cpp | 11 ++ src/core/file_sys/control_metadata.h | 1 + src/core/hle/service/acc/acc.cpp | 7 +- src/core/hle/service/erpt/erpt.cpp | 13 +- src/core/hle/service/friend/friend.cpp | 15 ++- .../ns/application_manager_interface.cpp | 7 +- .../ns/application_manager_interface.h | 2 + .../ns/content_management_interface.cpp | 9 ++ .../service/ns/content_management_interface.h | 4 + src/core/hle/service/ns/ns_types.h | 6 +- src/core/hle/service/ns/query_service.cpp | 37 ++++- src/core/hle/service/ns/query_service.h | 24 ++++ ...nly_application_control_data_interface.cpp | 127 +++++++++++++++++- ..._only_application_control_data_interface.h | 1 + .../hle/service/olsc/daemon_controller.cpp | 11 +- src/core/hle/service/olsc/daemon_controller.h | 2 +- src/core/hle/service/sockets/nsd.cpp | 15 ++- src/core/hle/service/sockets/nsd.h | 4 + src/core/launch_timestamp_cache.cpp | 55 +++++--- src/core/launch_timestamp_cache.h | 1 + 20 files changed, 313 insertions(+), 39 deletions(-) diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index f985943358..572c990acb 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -85,6 +88,14 @@ const LanguageEntry& NACP::GetLanguageEntry() const { return raw.language_entries.at(static_cast(Language::AmericanEnglish)); } +std::array NACP::GetApplicationNames() const { + std::array names{}; + for (size_t i = 0; i < raw.language_entries.size(); ++i) { + names[i] = raw.language_entries[i].GetApplicationName(); + } + return names; +} + std::string NACP::GetApplicationName() const { return GetLanguageEntry().GetApplicationName(); } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 25668e32c1..a06b014fac 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -131,6 +131,7 @@ public: u64 GetDefaultNormalSaveSize() const; u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; + std::array GetApplicationNames() const; std::vector GetRawBytes() const; bool GetUserAccountSwitchLock() const; u64 GetDeviceSaveDataSize() const; diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index d2070b0b74..c083133acc 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -138,12 +138,13 @@ private: R_SUCCEED(); } - Result GetNetworkServiceLicenseCacheEx() { + Result GetNetworkServiceLicenseCacheEx(Out out_license, Out out_expiration) { LOG_DEBUG(Service_ACC, "(STUBBED) called."); - // TODO (jarrodnorwell) + *out_license = 0; + *out_expiration = 0; - R_RETURN(ResultUnknown); + R_SUCCEED(); } Common::UUID account_id; diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp index 6b7eab5efd..3bcb3366e4 100644 --- a/src/core/hle/service/erpt/erpt.cpp +++ b/src/core/hle/service/erpt/erpt.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -22,7 +25,7 @@ public: {2, nullptr, "SetInitialLaunchSettingsCompletionTime"}, {3, nullptr, "ClearInitialLaunchSettingsCompletionTime"}, {4, nullptr, "UpdatePowerOnTime"}, - {5, nullptr, "UpdateAwakeTime"}, + {5, D<&ErrorReportContext::UpdateAwakeTime>, "UpdateAwakeTime"}, {6, nullptr, "SubmitMultipleCategoryContext"}, {7, nullptr, "UpdateApplicationLaunchTime"}, {8, nullptr, "ClearApplicationLaunchTime"}, @@ -74,6 +77,14 @@ private: report_type, unknown, create_report_option_flag); R_SUCCEED(); } + + Result UpdateAwakeTime(InBuffer data_a, + InBuffer data_b, u32 flag_a, u32 flag_b) { + LOG_WARNING(Service_SET, + "(STUBBED) called, data_a_size={}, data_b_size={}, flag_a={}, flag_b={}", + data_a.size(), data_b.size(), flag_a, flag_b); + R_SUCCEED(); + } }; class ErrorReportSession final : public ServiceFramework { diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index f65e70a969..b8f7cb1401 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -53,6 +53,7 @@ public: {20102, nullptr, "GetFriendDetailedInfo"}, {20103, nullptr, "SyncFriendList"}, {20104, &IFriendService::RequestSyncFriendList, "RequestSyncFriendList"}, + {20105, &IFriendService::GetFriendListForViewer, "GetFriendListForViewer"}, {20110, nullptr, "LoadFriendSetting"}, {20200, &IFriendService::GetReceivedFriendRequestCount, "GetReceivedFriendRequestCount"}, {20201, nullptr, "GetFriendRequestList"}, @@ -65,11 +66,13 @@ public: {20401, nullptr, "SyncBlockedUserList"}, {20500, nullptr, "GetProfileExtraList"}, {20501, nullptr, "GetRelationship"}, - {20600, &IFriendService::GetUserPresenceView, "GetUserPresenceView"}, + {20600, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV1"}, + {20601, &IFriendService::GetUserPresenceView, "GetUserPresenceViewV2"}, {20700, nullptr, "GetPlayHistoryList"}, {20701, &IFriendService::GetPlayHistoryStatistics, "GetPlayHistoryStatistics"}, - {20800, &IFriendService::LoadUserSetting, "LoadUserSetting"}, + {20800, &IFriendService::LoadUserSetting, "LoadUserSettingV1"}, {20801, nullptr, "SyncUserSetting"}, + {20802, &IFriendService::LoadUserSetting, "LoadUserSettingV2"}, {20900, &IFriendService::RequestListSummaryOverlayNotification, "RequestListSummaryOverlayNotification"}, {21000, nullptr, "GetExternalApplicationCatalog"}, {22000, nullptr, "GetReceivedFriendInvitationList"}, @@ -270,6 +273,14 @@ private: rb.Push(ResultSuccess); } + void GetFriendListForViewer(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } + void GetReceivedFriendRequestCount(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; [[maybe_unused]] const auto uuid = rp.PopRaw(); diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index f989bdc0a8..12cf7f4257 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -136,7 +136,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {404, nullptr, "InvalidateApplicationControlCache"}, {405, nullptr, "ListApplicationControlCacheEntryInfo"}, {406, nullptr, "GetApplicationControlProperty"}, - {407, nullptr, "ListApplicationTitle"}, + {407, &IApplicationManagerInterface::ListApplicationTitle, "ListApplicationTitle"}, {408, nullptr, "ListApplicationIcon"}, {411, nullptr, "Unknown411"}, //19.0.0+ {412, nullptr, "Unknown412"}, //19.0.0+ @@ -832,4 +832,9 @@ Result IApplicationManagerInterface::Unknown4053() { R_SUCCEED(); } +void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index e7bab6dca1..0780fb7b8b 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -71,6 +71,8 @@ public: Result RequestDownloadApplicationControlDataInBackground(u64 control_source, u64 application_id); + void ListApplicationTitle(HLERequestContext& ctx); + private: KernelHelpers::ServiceContext service_context; Event record_update_system_event; diff --git a/src/core/hle/service/ns/content_management_interface.cpp b/src/core/hle/service/ns/content_management_interface.cpp index 09c15a3e0f..18b75e9d73 100644 --- a/src/core/hle/service/ns/content_management_interface.cpp +++ b/src/core/hle/service/ns/content_management_interface.cpp @@ -21,6 +21,7 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_) {43, D<&IContentManagementInterface::CheckSdCardMountStatus>, "CheckSdCardMountStatus"}, {47, D<&IContentManagementInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"}, {48, D<&IContentManagementInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"}, + {71, D<&IContentManagementInterface::GetUnknown71>, "Unknown71"}, {600, nullptr, "CountApplicationContentMeta"}, {601, nullptr, "ListApplicationContentMetaStatus"}, {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, @@ -73,4 +74,12 @@ Result IContentManagementInterface::GetFreeSpaceSize(Out out_free_space_siz R_SUCCEED(); } +Result IContentManagementInterface::GetUnknown71(Out out_value_a, Out out_value_b, + u8 flag) { + LOG_INFO(Service_NS, "(STUBBED) called, flag={:02X}", flag); + *out_value_a = 0; + *out_value_b = 0; + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/content_management_interface.h b/src/core/hle/service/ns/content_management_interface.h index 2894628e52..2ffac6f3b4 100644 --- a/src/core/hle/service/ns/content_management_interface.h +++ b/src/core/hle/service/ns/content_management_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,6 +23,7 @@ public: Result CheckSdCardMountStatus(); Result GetTotalSpaceSize(Out out_total_space_size, FileSys::StorageId storage_id); Result GetFreeSpaceSize(Out out_free_space_size, FileSys::StorageId storage_id); + Result GetUnknown71(Out out_value_a, Out out_value_b, u8 flag); }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h index c581e8d6c3..ade0935622 100644 --- a/src/core/hle/service/ns/ns_types.h +++ b/src/core/hle/service/ns/ns_types.h @@ -47,9 +47,9 @@ struct ApplicationDownloadState { u64 total_size; u32 unk_x10; u8 state; - u8 unk_x19; - std::array unk_x1a; - u64 unk_x20; + u8 unk_x15; + std::array unk_x16; + u64 unk_x18; }; static_assert(sizeof(ApplicationDownloadState) == 0x20, "ApplicationDownloadState has incorrect size."); diff --git a/src/core/hle/service/ns/query_service.cpp b/src/core/hle/service/ns/query_service.cpp index a4632cb6c8..af32d7801d 100644 --- a/src/core/hle/service/ns/query_service.cpp +++ b/src/core/hle/service/ns/query_service.cpp @@ -9,10 +9,13 @@ #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/query_service.h" #include "core/hle/service/service.h" +#include "core/launch_timestamp_cache.h" +#include "frontend_common/play_time_manager.h" namespace Service::NS { -IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, "pdm:qry"} { +IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, "pdm:qry"}, + play_time_manager{std::make_unique()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "QueryAppletEvent"}, @@ -33,8 +36,8 @@ IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, {15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"}, {16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"}, {17, D<&IQueryService::QueryLastPlayTime>, "QueryLastPlayTime"}, - {18, nullptr, "QueryApplicationPlayStatisticsForSystem"}, - {19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, + {18, D<&IQueryService::QueryApplicationPlayStatisticsForSystem>, "QueryApplicationPlayStatisticsForSystem"}, + {19, D<&IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem>, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, }; // clang-format on @@ -65,4 +68,32 @@ Result IQueryService::QueryLastPlayTime( R_SUCCEED(); } +Result IQueryService::QueryApplicationPlayStatisticsForSystem( + Out out_entries, u8 flag, + OutArray out_stats, + InArray application_ids) { + const size_t count = std::min(out_stats.size(), application_ids.size()); + s32 written = 0; + for (size_t i = 0; i < count; ++i) { + const u64 app_id = application_ids[i]; + ApplicationPlayStatistics stats{}; + stats.application_id = app_id; + stats.play_time_ns = play_time_manager->GetPlayTime(app_id) * 1'000'000'000ULL; + stats.launch_count = Core::LaunchTimestampCache::GetLaunchCount(app_id); + out_stats[i] = stats; + ++written; + } + *out_entries = written; + LOG_DEBUG(Service_NS, "called, entries={} flag={}", written, flag); + R_SUCCEED(); +} + +Result IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem( + Out out_entries, u8 flag, Common::UUID user_id, + OutArray out_stats, + InArray application_ids) { + // well we don't do per-user tracking :> + return QueryApplicationPlayStatisticsForSystem(out_entries, flag, out_stats, application_ids); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/query_service.h b/src/core/hle/service/ns/query_service.h index ba1cddd4ca..6266e61c4d 100644 --- a/src/core/hle/service/ns/query_service.h +++ b/src/core/hle/service/ns/query_service.h @@ -7,10 +7,17 @@ #pragma once #include "common/uuid.h" +#include "core/hle/service/am/am_types.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/service.h" +#include + +namespace PlayTime { +class PlayTimeManager; +} + namespace Service::NS { struct PlayStatistics { @@ -28,6 +35,13 @@ static_assert(sizeof(PlayStatistics) == 0x28, "PlayStatistics is an invalid size struct LastPlayTime {}; +struct ApplicationPlayStatistics { + u64 application_id{}; + u64 play_time_ns{}; + u64 launch_count{}; +}; +static_assert(sizeof(ApplicationPlayStatistics) == 0x18, "ApplicationPlayStatistics is an invalid size"); + class IQueryService final : public ServiceFramework { public: explicit IQueryService(Core::System& system_); @@ -39,6 +53,16 @@ private: Result QueryLastPlayTime(Out out_entries, u8 unknown, OutArray out_last_play_times, InArray application_ids); + Result QueryApplicationPlayStatisticsForSystem( + Out out_entries, u8 flag, + OutArray out_stats, + InArray application_ids); + Result QueryApplicationPlayStatisticsByUserAccountIdForSystem( + Out out_entries, u8 flag, Common::UUID user_id, + OutArray out_stats, + InArray application_ids); + + std::unique_ptr play_time_manager; }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp index 0115b83fd0..42b704588d 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp @@ -6,20 +6,24 @@ #include #include +#include #include #include #include +#include #include "common/settings.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs/vfs.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/set/settings_server.h" +#include "core/hle/service/kernel_helpers.h" namespace Service::NS { @@ -66,6 +70,70 @@ void SanitizeJPEGImageSize(std::vector& image) { } // namespace + +// IAsyncValue implementation for ListApplicationTitle +// https://switchbrew.org/wiki/NS_services#ListApplicationTitle +class IAsyncValueForListApplicationTitle final : public ServiceFramework { +public: + explicit IAsyncValueForListApplicationTitle(Core::System& system_, s32 offset, s32 size) + : ServiceFramework{system_, "IAsyncValue"}, service_context{system_, "IAsyncValue"}, + data_offset{offset}, data_size{size} { + static const FunctionInfo functions[] = { + {0, &IAsyncValueForListApplicationTitle::GetSize, "GetSize"}, + {1, &IAsyncValueForListApplicationTitle::Get, "Get"}, + {2, &IAsyncValueForListApplicationTitle::Cancel, "Cancel"}, + {3, &IAsyncValueForListApplicationTitle::GetErrorContext, "GetErrorContext"}, + }; + RegisterHandlers(functions); + + completion_event = service_context.CreateEvent("IAsyncValue:Completion"); + completion_event->GetReadableEvent().Signal(); + } + + ~IAsyncValueForListApplicationTitle() override { + service_context.CloseEvent(completion_event); + } + + Kernel::KReadableEvent& ReadableEvent() const { + return completion_event->GetReadableEvent(); + } + +private: + void GetSize(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(data_size); + } + + void Get(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + std::vector buffer(sizeof(s32)); + std::memcpy(buffer.data(), &data_offset, sizeof(s32)); + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Cancel(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetErrorContext(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* completion_event{}; + s32 data_offset; + s32 data_size; +}; + IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface( Core::System& system_) : ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} { @@ -76,8 +144,9 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, - {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlDataWithoutIcon"}, - {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlDataWithoutIcon3"}, + {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlData"}, + {13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"}, + {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, }; // clang-format on @@ -240,6 +309,60 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2( R_SUCCEED(); } + +void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) { + /* + IPC::RequestParser rp{ctx}; + auto control_source = rp.PopRaw(); + rp.Skip(7, false); + auto transfer_memory_size = rp.Pop(); + */ + + const auto app_ids_buffer = ctx.ReadBuffer(); + const size_t app_count = app_ids_buffer.size() / sizeof(u64); + + std::vector application_ids(app_count); + if (app_count > 0) { + std::memcpy(application_ids.data(), app_ids_buffer.data(), app_count * sizeof(u64)); + } + + auto t_mem_obj = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); + auto* t_mem = t_mem_obj.GetPointerUnsafe(); + + constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry); + const size_t total_data_size = app_count * title_entry_size; + + constexpr s32 data_offset = 0; + + if (t_mem != nullptr && app_count > 0) { + auto& memory = system.ApplicationMemory(); + const auto t_mem_address = t_mem->GetSourceAddress(); + + for (size_t i = 0; i < app_count; ++i) { + const u64 app_id = application_ids[i]; + const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + FileSys::LanguageEntry entry{}; + if (control.first != nullptr) { + entry = control.first->GetLanguageEntry(); + } + + const size_t offset = i * title_entry_size; + memory.WriteBlock(t_mem_address + offset, &entry, title_entry_size); + } + } + + auto async_value = std::make_shared( + system, data_offset, static_cast(total_data_size)); + + IPC::ResponseBuilder rb{ctx, 2, 1, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(async_value->ReadableEvent()); + rb.PushIpcInterface(std::move(async_value)); +} + Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( OutBuffer out_buffer, Out out_flags_a, Out out_flags_b, Out out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) { diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index 4da45736b8..a496444318 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -34,6 +34,7 @@ public: u8 flag1, u8 flag2, u64 application_id); + void ListApplicationTitle(HLERequestContext& ctx); Result GetApplicationControlData3( OutBuffer out_buffer, Out out_flags_a, diff --git a/src/core/hle/service/olsc/daemon_controller.cpp b/src/core/hle/service/olsc/daemon_controller.cpp index 45f0f75b94..149a6b1230 100644 --- a/src/core/hle/service/olsc/daemon_controller.cpp +++ b/src/core/hle/service/olsc/daemon_controller.cpp @@ -92,13 +92,10 @@ Result IDaemonController::SetGlobalAutoDownloadSetting(bool is_enabled, Common:: R_SUCCEED(); } -Result IDaemonController::GetAutonomyTaskStatus(Out out_state, Common::UUID user_id, - u64 application_id) { - LOG_INFO(Service_OLSC, "called, user_id={} application_id={:016X}", user_id.FormattedString(), - application_id); - AppKey key{user_id, application_id}; - const auto it = autonomy_task_status_.find(key); - *out_state = (it != autonomy_task_status_.end()) ? it->second : 0; // default idle +Result IDaemonController::GetAutonomyTaskStatus(Out out_status, Common::UUID user_id) { + LOG_INFO(Service_OLSC, "called, user_id={}", user_id.FormattedString()); + + *out_status = 0; R_SUCCEED(); } diff --git a/src/core/hle/service/olsc/daemon_controller.h b/src/core/hle/service/olsc/daemon_controller.h index 7e5416562e..45aa0f16c2 100644 --- a/src/core/hle/service/olsc/daemon_controller.h +++ b/src/core/hle/service/olsc/daemon_controller.h @@ -32,7 +32,7 @@ private: Result GetGlobalAutoDownloadSetting(Out out_is_enabled, Common::UUID user_id); Result SetGlobalAutoDownloadSetting(bool is_enabled, Common::UUID user_id); - Result GetAutonomyTaskStatus(Out out_state, Common::UUID user_id, u64 application_id); + Result GetAutonomyTaskStatus(Out out_status, Common::UUID user_id); // Internal in-memory state to back the above APIs struct AppKey { diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 491b76d481..10f7eb1e8d 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,7 +37,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na {12, nullptr, "GetDeviceId"}, {13, nullptr, "DeleteSettings"}, {14, nullptr, "ImportSettings"}, - {15, nullptr, "SetChangeEnvironmentIdentifierDisabled"}, + {15, &NSD::SetChangeEnvironmentIdentifierDisabled, "SetChangeEnvironmentIdentifierDisabled"}, {20, &NSD::Resolve, "Resolve"}, {21, &NSD::ResolveEx, "ResolveEx"}, {30, nullptr, "GetNasServiceSetting"}, @@ -77,6 +80,16 @@ static Result ResolveCommon(const std::string& fqdn_in, std::array& return ResultSuccess; } +void NSD::SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const bool disabled = rp.Pop(); + + LOG_WARNING(Service, "(STUBBED) called, disabled={}", disabled); + + IPC::ResponseBuilder rb{ctx, 1}; + rb.Push(ResultSuccess); +} + void NSD::Resolve(HLERequestContext& ctx) { const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0)); diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index b0cfec507f..3bc3cf87a5 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -17,6 +20,7 @@ public: ~NSD() override; private: + void SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx); void Resolve(HLERequestContext& ctx); void ResolveEx(HLERequestContext& ctx); void GetEnvironmentIdentifier(HLERequestContext& ctx); diff --git a/src/core/launch_timestamp_cache.cpp b/src/core/launch_timestamp_cache.cpp index 5a154a5c2f..037992d69a 100644 --- a/src/core/launch_timestamp_cache.cpp +++ b/src/core/launch_timestamp_cache.cpp @@ -22,10 +22,12 @@ namespace Core::LaunchTimestampCache { namespace { using CacheMap = std::unordered_map; +using CountMap = std::unordered_map; -std::mutex mutex; -CacheMap cache; -bool loaded = false; +std::mutex g_mutex; +CacheMap g_cache; +CountMap g_counts; +bool g_loaded = false; std::filesystem::path GetCachePath() { return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "launched.json"; @@ -54,11 +56,11 @@ bool WriteStringToFile(const std::filesystem::path& path, const std::string& dat } void Load() { - if (loaded) { + if (g_loaded) { return; } - loaded = true; + g_loaded = true; const auto path = GetCachePath(); if (!std::filesystem::exists(path)) { @@ -86,19 +88,31 @@ void Load() { } catch (...) { continue; } - if (value.is_number_integer()) { - cache[key] = value.get(); + if (value.is_object()) { + if (value.contains("timestamp") && value["timestamp"].is_number_integer()) { + g_cache[key] = value["timestamp"].get(); + } + if (value.contains("launch_count") && value["launch_count"].is_number_unsigned()) { + g_counts[key] = value["launch_count"].get(); + } + } else if (value.is_number_integer()) { + // Legacy format: raw timestamp only + g_cache[key] = value.get(); } } } catch (const std::exception& e) { - LOG_WARNING(Core, "Failed to parse launch timestamp cache"); + LOG_WARNING(Core, "Failed to parse launch timestamp cache: {}", e.what()); } } void Save() { nlohmann::json json = nlohmann::json::object(); - for (const auto& [key, value] : cache) { - json[fmt::format("{:016X}", key)] = value; + for (const auto& [key, value] : g_cache) { + nlohmann::json entry = nlohmann::json::object(); + entry["timestamp"] = value; + const auto count_it = g_counts.find(key); + entry["launch_count"] = count_it != g_counts.end() ? count_it->second : 0; + json[fmt::format("{:016X}", key)] = entry; } const auto path = GetCachePath(); @@ -115,21 +129,32 @@ s64 NowSeconds() { } // namespace void SaveLaunchTimestamp(u64 title_id) { - std::scoped_lock lk{mutex}; + std::scoped_lock lk{g_mutex}; Load(); - cache[title_id] = NowSeconds(); + g_cache[title_id] = NowSeconds(); + g_counts[title_id] = g_counts[title_id] + 1; Save(); } s64 GetLaunchTimestamp(u64 title_id) { - std::scoped_lock lk{mutex}; + std::scoped_lock lk{g_mutex}; Load(); - const auto it = cache.find(title_id); - if (it != cache.end()) { + const auto it = g_cache.find(title_id); + if (it != g_cache.end()) { return it->second; } // we need a timestamp, i decided on 01/01/2026 00:00 return 1767225600; } +u64 GetLaunchCount(u64 title_id) { + std::scoped_lock lk{g_mutex}; + Load(); + const auto it = g_counts.find(title_id); + if (it != g_counts.end()) { + return it->second; + } + return 0; +} + } // namespace Core::LaunchTimestampCache diff --git a/src/core/launch_timestamp_cache.h b/src/core/launch_timestamp_cache.h index b3722033cb..d3563c7eb4 100644 --- a/src/core/launch_timestamp_cache.h +++ b/src/core/launch_timestamp_cache.h @@ -9,5 +9,6 @@ namespace Core::LaunchTimestampCache { void SaveLaunchTimestamp(u64 title_id); s64 GetLaunchTimestamp(u64 title_id); +u64 GetLaunchCount(u64 title_id); } // namespace Core::LaunchTimestampCache