aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/file_sys/control_metadata.cpp11
-rw-r--r--src/core/file_sys/control_metadata.h1
-rw-r--r--src/core/hle/service/acc/acc.cpp7
-rw-r--r--src/core/hle/service/erpt/erpt.cpp13
-rw-r--r--src/core/hle/service/friend/friend.cpp15
-rw-r--r--src/core/hle/service/ns/application_manager_interface.cpp7
-rw-r--r--src/core/hle/service/ns/application_manager_interface.h2
-rw-r--r--src/core/hle/service/ns/content_management_interface.cpp9
-rw-r--r--src/core/hle/service/ns/content_management_interface.h4
-rw-r--r--src/core/hle/service/ns/ns_types.h6
-rw-r--r--src/core/hle/service/ns/query_service.cpp37
-rw-r--r--src/core/hle/service/ns/query_service.h24
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.cpp127
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.h1
-rw-r--r--src/core/hle/service/olsc/daemon_controller.cpp11
-rw-r--r--src/core/hle/service/olsc/daemon_controller.h2
-rw-r--r--src/core/hle/service/sockets/nsd.cpp15
-rw-r--r--src/core/hle/service/sockets/nsd.h4
-rw-r--r--src/core/launch_timestamp_cache.cpp55
-rw-r--r--src/core/launch_timestamp_cache.h1
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<u8>(Language::AmericanEnglish));
}
+std::array<std::string, 16> NACP::GetApplicationNames() const {
+ std::array<std::string, 16> 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<std::string, 16> GetApplicationNames() const;
std::vector<u8> 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<u32> out_license, Out<s64> 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<BufferAttr_HipcMapAlias> data_a,
+ InBuffer<BufferAttr_HipcMapAlias> 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<ErrorReportSession> {
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<u32>(0);
+ }
+
void GetReceivedFriendRequestCount(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto uuid = rp.PopRaw<Common::UUID>();
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<s64> out_free_space_siz
R_SUCCEED();
}
+Result IContentManagementInterface::GetUnknown71(Out<u64> out_value_a, Out<u64> 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<s64> out_total_space_size, FileSys::StorageId storage_id);
Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
+ Result GetUnknown71(Out<u64> out_value_a, Out<u64> 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<u8, 0x2> unk_x1a;
- u64 unk_x20;
+ u8 unk_x15;
+ std::array<u8, 0x2> 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<PlayTime::PlayTimeManager>()} {
// 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<s32> out_entries, u8 flag,
+ OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
+ InArray<u64, BufferAttr_HipcMapAlias> 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<s32> out_entries, u8 flag, Common::UUID user_id,
+ OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
+ InArray<u64, BufferAttr_HipcMapAlias> 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 <memory>
+
+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<IQueryService> {
public:
explicit IQueryService(Core::System& system_);
@@ -39,6 +53,16 @@ private:
Result QueryLastPlayTime(Out<s32> out_entries, u8 unknown,
OutArray<LastPlayTime, BufferAttr_HipcMapAlias> out_last_play_times,
InArray<s32, BufferAttr_HipcMapAlias> application_ids);
+ Result QueryApplicationPlayStatisticsForSystem(
+ Out<s32> out_entries, u8 flag,
+ OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids);
+ Result QueryApplicationPlayStatisticsByUserAccountIdForSystem(
+ Out<s32> out_entries, u8 flag, Common::UUID user_id,
+ OutArray<ApplicationPlayStatistics, BufferAttr_HipcMapAlias> out_stats,
+ InArray<u64, BufferAttr_HipcMapAlias> application_ids);
+
+ std::unique_ptr<PlayTime::PlayTimeManager> 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 <algorithm>
#include <vector>
+#include <optional>
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
+#include <string>
#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<u8>& image) {
} // namespace
+
+// IAsyncValue implementation for ListApplicationTitle
+// https://switchbrew.org/wiki/NS_services#ListApplicationTitle
+class IAsyncValueForListApplicationTitle final : public ServiceFramework<IAsyncValueForListApplicationTitle> {
+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<s64>(data_size);
+ }
+
+ void Get(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NS, "called");
+ std::vector<u8> 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<u8>();
+ rp.Skip(7, false);
+ auto transfer_memory_size = rp.Pop<u64>();
+ */
+
+ const auto app_ids_buffer = ctx.ReadBuffer();
+ const size_t app_count = app_ids_buffer.size() / sizeof(u64);
+
+ std::vector<u64> 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<Kernel::KTransferMemory>(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<IAsyncValueForListApplicationTitle>(
+ system, data_offset, static_cast<s32>(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<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b,
Out<u32> 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<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> 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<u8> 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<u8> 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<bool> out_is_enabled, Common::UUID user_id);
Result SetGlobalAutoDownloadSetting(bool is_enabled, Common::UUID user_id);
- Result GetAutonomyTaskStatus(Out<u8> out_state, Common::UUID user_id, u64 application_id);
+ Result GetAutonomyTaskStatus(Out<u8> 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<char, 0x100>&
return ResultSuccess;
}
+void NSD::SetChangeEnvironmentIdentifierDisabled(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const bool disabled = rp.Pop<bool>();
+
+ 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<u64, s64>;
+using CountMap = std::unordered_map<u64, u64>;
-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<s64>();
+ if (value.is_object()) {
+ if (value.contains("timestamp") && value["timestamp"].is_number_integer()) {
+ g_cache[key] = value["timestamp"].get<s64>();
+ }
+ if (value.contains("launch_count") && value["launch_count"].is_number_unsigned()) {
+ g_counts[key] = value["launch_count"].get<u64>();
+ }
+ } else if (value.is_number_integer()) {
+ // Legacy format: raw timestamp only
+ g_cache[key] = value.get<s64>();
}
}
} 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