From 62643c3424c0b0f73261785af879d9288127683d Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 8 Oct 2026 04:29:49 +0400 Subject: [PATCH] system service: deliver initial AppContent entitlement updates through shared queue --- src/libs/libAppContent.cpp | 2 ++ src/libs/libSystemService.cpp | 24 ++++++++++++++- src/libs/systemService.h | 10 +++++++ tests/KernelFileSystemTests.cpp | 53 +++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/libs/systemService.h diff --git a/src/libs/libAppContent.cpp b/src/libs/libAppContent.cpp index d76c0c7f7..645faf05c 100644 --- a/src/libs/libAppContent.cpp +++ b/src/libs/libAppContent.cpp @@ -6,6 +6,7 @@ #include "common/stringUtils.h" #include "libs/errno.h" #include "libs/libs.h" +#include "libs/systemService.h" #include "loader/symbolDatabase.h" #include "loader/systemContent.h" @@ -96,6 +97,7 @@ int KYTY_SYSV_ABI AppContentInitialize(const AppContentInitParam* init_param, LOGF("\t TITLE_ID missing\n"); } + SystemService::NotifyEntitlementUpdate(); return OK; } diff --git a/src/libs/libSystemService.cpp b/src/libs/libSystemService.cpp index 655096dc6..690b813be 100644 --- a/src/libs/libSystemService.cpp +++ b/src/libs/libSystemService.cpp @@ -7,9 +7,12 @@ #include "libs/dialog.h" #include "libs/errno.h" #include "libs/libs.h" +#include "libs/systemService.h" #include "loader/symbolDatabase.h" #include +#include +#include namespace Libs { @@ -70,6 +73,14 @@ struct SystemServiceEvent { uint8_t data[8192]; }; +static std::mutex g_event_mutex; +static std::deque g_events; + +void NotifyEntitlementUpdate() { + std::scoped_lock lock(g_event_mutex); + g_events.push_back(0x10000003); // SCE_SYSTEM_SERVICE_EVENT_ENTITLEMENT_UPDATE +} + struct SystemServiceDisplaySafeAreaInfo { float ratio; uint8_t reserved[128]; @@ -153,7 +164,14 @@ static int KYTY_SYSV_ABI SystemServiceReceiveEvent(SystemServiceEvent* event) { event->event_type = -1; std::memset(event->data, 0, sizeof(event->data)); - return SYSTEM_SERVICE_ERROR_NO_EVENT; + std::scoped_lock lock(g_event_mutex); + if (g_events.empty()) { + return SYSTEM_SERVICE_ERROR_NO_EVENT; + } + event->event_type = g_events.front(); + g_events.pop_front(); + LOGF("\t SystemService event = 0x%08x\n", static_cast(event->event_type)); + return OK; } static int KYTY_SYSV_ABI SystemServiceGetStatus(SystemServiceStatus* status) { @@ -167,6 +185,10 @@ static int KYTY_SYSV_ABI SystemServiceGetStatus(SystemServiceStatus* status) { *status = SystemServiceStatus(); status->is_system_ui_overlaid = dialog.active; status->is_in_background_execution = dialog.background; + { + std::scoped_lock lock(g_event_mutex); + status->event_num = static_cast(g_events.size()); + } return OK; } diff --git a/src/libs/systemService.h b/src/libs/systemService.h new file mode 100644 index 000000000..5a93f5f4c --- /dev/null +++ b/src/libs/systemService.h @@ -0,0 +1,10 @@ +#ifndef EMULATOR_LIBS_SYSTEMSERVICE_H_ +#define EMULATOR_LIBS_SYSTEMSERVICE_H_ + +namespace Libs::SystemService { + +void NotifyEntitlementUpdate(); + +} // namespace Libs::SystemService + +#endif diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index f0f7cb976..9c2ee6504 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -46,6 +46,8 @@ void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols); namespace Libs { void InitLibKernel_1(Loader::SymbolDatabase *symbols); void InitSysmodule_1(Loader::SymbolDatabase *symbols); +void InitSystemService_1(Loader::SymbolDatabase *symbols); +void InitAppContent_1(Loader::SymbolDatabase *symbols); } namespace Libs::LibAmpr { @@ -204,6 +206,56 @@ void TestSysmoduleReferences() { "internal and public module operations share load state"); } +void TestSystemServiceEntitlementEvents() { + Loader::SymbolDatabase symbols; + Libs::InitSystemService_1(&symbols); + Libs::InitAppContent_1(&symbols); + const auto resolve = [&](const char* nid, const char* library, const char* module) { + const auto* symbol = symbols.Find( + {nid, library, 1, module, 1, 1, Loader::SymbolType::Func}); + Check(symbol != nullptr, "entitlement event export resolves"); + return symbol->vaddr; + }; + const auto initialize = reinterpret_cast( + resolve("R9lA82OraNs", "AppContent", "AppContentUtil")); + struct Status { + int32_t event_num; + bool overlay, background, vr; + uint8_t reserved[127]; + }; + struct Event { + int32_t type; + uint8_t data[8192]; + }; + static_assert(sizeof(Status) == 136 && sizeof(Event) == 8196); + const auto get_status = reinterpret_cast( + resolve("rPo6tV8D9bM", "SystemService", "SystemService")); + const auto receive = reinterpret_cast( + resolve("656LMQSrg6U", "SystemService", "SystemService")); + Status status {}; + Event event {}; + Check(get_status(&status) == OK && status.event_num == 0, + "SystemService starts without pending events"); + std::array init {}; + std::array boot {}; + Check(initialize(init.data(), boot.data()) == OK, + "AppContent initialization generates an entitlement notification"); + Check(get_status(&status) == OK && status.event_num == 1 && + get_status(&status) == OK && status.event_num == 1, + "status queries preserve pending notifications"); + Check(receive(nullptr) == Libs::SystemService::SYSTEM_SERVICE_ERROR_PARAMETER && + get_status(&status) == OK && status.event_num == 1, + "invalid event output does not consume a notification"); + std::memset(&event, 0xff, sizeof(event)); + Check(receive(&event) == OK && event.type == 0x10000003 && + std::all_of(std::begin(event.data), std::end(event.data), + [](uint8_t byte) { return byte == 0; }) && + get_status(&status) == OK && status.event_num == 0, + "receive delivers the entitlement event with cleared payload exactly once"); + Check(receive(&event) == Libs::SystemService::SYSTEM_SERVICE_ERROR_NO_EVENT, + "empty event queue reports NO_EVENT"); +} + void TestSaveOpenVisibility() { constexpr char Path[] = "/savedata0/visible-save.dat"; constexpr char Payload[] = "saved progress"; @@ -1573,6 +1625,7 @@ int main(int, char**) { TempDirectory temporary; FileSystem::Initialize(); TestSysmoduleReferences(); + TestSystemServiceEntitlementEvents(); TestRandomDevices(); TestFileDescriptorFlags(); CheckMountRoot(temporary.Path());