Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/libs/prx/libSceUserService/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ endif()
if(BUILD_TESTING)
add_test_executable(guest_user_service_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceLifecycle.cpp)
target_include_directories(guest_user_service_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceUserService libSceSystemService)
target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceSystemService libc libkernel)
configure_windows_unwind(guest_user_service_lifecycle_tests)
add_test(NAME guest_user_service_lifecycle COMMAND guest_user_service_lifecycle_tests)
set_tests_properties(guest_user_service_lifecycle PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_user_service_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUserService>")
Expand Down
281 changes: 244 additions & 37 deletions core/libs/prx/libSceUserService/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@
#include <cstring>
#include <deque>
#include <mutex>
#include <vector>
#include <shared_mutex>
#include <memory>
#include <condition_variable>
#include <algorithm>
#include <thread>
#include "prx/libSceUserService/UserService.hpp"

namespace {

std::atomic<bool> initialized{false};
bool terminating{};
std::mutex userMutex;

struct UserEvent {
Expand Down Expand Up @@ -108,35 +115,184 @@ bool UserGroupSlotExists(int index) {
FileStat status{};
return sceKernelStat(path, &status) == 0;
}

struct RegisteredEventCallback {
UserServiceEventCallback callback;
void* userData;
std::mutex mutex;
std::condition_variable idle;
bool active{true};
std::size_t inFlight{};
};

std::vector<std::shared_ptr<RegisteredEventCallback>> eventCallbacks;
std::shared_mutex eventCallbacksMutex;
struct CallbackInvocation {
const RegisteredEventCallback* entry;
CallbackInvocation* previous;
};
thread_local CallbackInvocation* callbacksOnThisThread{};

std::size_t CurrentThreadCallbackDepth(const RegisteredEventCallback* entry) {
std::size_t depth{};
for (auto* invocation = callbacksOnThisThread; invocation != nullptr; invocation = invocation->previous) {
depth += invocation->entry == entry;
}
return depth;
}

void DeactivateAndWait(const std::shared_ptr<RegisteredEventCallback>& entry) {
std::unique_lock lock(entry->mutex);
entry->active = false;
const std::size_t currentThreadDepth = CurrentThreadCallbackDepth(entry.get());
entry->idle.wait(lock, [&] { return entry->inFlight <= currentThreadDepth; });
}


void RemoveDrainedCallbacks();

void DispatchUserEvent(const std::vector<std::shared_ptr<RegisteredEventCallback>>& callbacks,
const SceUserServiceEvent& event) {
for (const auto& entry : callbacks) {
UserServiceEventCallback callback;
void* userData;
{
std::lock_guard lock(entry->mutex);
if (!entry->active) {
continue;
}
++entry->inFlight;
callback = entry->callback;
userData = entry->userData;
}
CallbackInvocation invocation{entry.get(), callbacksOnThisThread};
callbacksOnThisThread = &invocation;
try {
callback(&event, userData);
} catch (...) {
callbacksOnThisThread = invocation.previous;
{
std::lock_guard lock(entry->mutex);
--entry->inFlight;
}
entry->idle.notify_all();
RemoveDrainedCallbacks();
throw;
}
callbacksOnThisThread = invocation.previous;
{
std::lock_guard lock(entry->mutex);
--entry->inFlight;
}
entry->idle.notify_all();
RemoveDrainedCallbacks();
}
}
struct CallbackNotification {
std::vector<std::shared_ptr<RegisteredEventCallback>> callbacks;
SceUserServiceEvent event;
};

class EventDispatcher {
std::mutex mutex;
std::condition_variable changed;
std::deque<CallbackNotification> notifications;
bool stopping{};
std::thread worker;
public:
EventDispatcher() : worker([this] {
std::unique_lock lock(mutex);
while (true) {
changed.wait(lock, [&] { return stopping || !notifications.empty(); });
if (stopping) return;
auto notification = std::move(notifications.front());
notifications.pop_front();
lock.unlock();
DispatchUserEvent(notification.callbacks, notification.event);
lock.lock();
}
}) {}
~EventDispatcher() {
{
std::lock_guard lock(mutex);
stopping = true;
}
changed.notify_all();
worker.join();
}
void Enqueue(CallbackNotification notification) {
{
std::lock_guard lock(mutex);
notifications.push_back(std::move(notification));
}
changed.notify_one();
}
};

void NotifyUserEvent(std::uint32_t eventType, int userId) {
std::shared_lock lock(eventCallbacksMutex);
if (!initialized.load() || eventCallbacks.empty()) return;
static EventDispatcher dispatcher;
dispatcher.Enqueue({eventCallbacks, {eventType, userId}});
}

void RemoveDrainedCallbacks() {
std::unique_lock lock(eventCallbacksMutex);
std::erase_if(eventCallbacks, [](const auto& entry) {
std::lock_guard entryLock(entry->mutex);
return !entry->active && entry->inFlight == 0;
});
}

void ClearEventCallbacks() {
std::vector<std::shared_ptr<RegisteredEventCallback>> callbacks;
{
std::unique_lock lock(eventCallbacksMutex);
callbacks = eventCallbacks;
for (const auto& entry : callbacks) {
std::lock_guard entryLock(entry->mutex);
entry->active = false;
}
}
for (const auto& entry : callbacks) {
DeactivateAndWait(entry);
}
RemoveDrainedCallbacks();
}
}

extern "C" {

void UserServiceSetUserConnected_nid_postfix(int userIndex, bool connected) {
if (userIndex < 0 || userIndex >= USER_SERVICE_MAX_USERS) return;
std::lock_guard lock(userMutex);
if (userConnected[userIndex] != connected) {
userConnected[userIndex] = connected;
if (initialized.load()) {
constexpr std::uint32_t EventTypeLogin = 0;
constexpr std::uint32_t EventTypeLogout = 1;
const std::uint32_t eventType = connected ? EventTypeLogin : EventTypeLogout;
const int userId = USER_SERVICE_INITIAL_USER_ID + userIndex;
bool collapsed = false;
for (auto it = eventQueue.begin(); it != eventQueue.end(); ++it) {
if (it->userId == userId) {
if (it->eventType != eventType) {
eventQueue.erase(it);
bool notify = false;
const std::uint32_t eventType = connected ? 0u : 1u;
const int userId = USER_SERVICE_INITIAL_USER_ID + userIndex;
{
std::lock_guard lock(userMutex);
if (userConnected[userIndex] != connected) {
userConnected[userIndex] = connected;
if (initialized.load()) {
notify = true;
bool collapsed = false;
for (auto it = eventQueue.begin(); it != eventQueue.end(); ++it) {
if (it->userId == userId) {
if (it->eventType != eventType) {
eventQueue.erase(it);
}
collapsed = true;
break;
}
collapsed = true;
break;
}
}
if (!collapsed) {
eventQueue.push_back({eventType, userId});
if (!collapsed) {
eventQueue.push_back({eventType, userId});
}
}
}
}
if (notify) {
NotifyUserEvent(eventType, userId);
}
}

int APS5_VABI sceUserServiceGetAccessibilityChatTranscription(int user_id, int32_t* chat_transcription) {
Expand Down Expand Up @@ -278,7 +434,8 @@ static bool ValidThreadPriority(int priority) {
}

static int MarkInitialized() {
if (initialized.exchange(true)) {
std::unique_lock lock(eventCallbacksMutex);
if (terminating || initialized.exchange(true)) {
return USER_SERVICE_ERROR_ALREADY_INITIALIZED;
}
PopulateInitialLoginEvents();
Expand All @@ -302,11 +459,22 @@ int APS5_VABI sceUserServiceInitialize2(std::int32_t priority, KernelCpumask aff
}

int APS5_VABI sceUserServiceTerminate(void) {
if (!initialized.exchange(false)) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
{
std::unique_lock lock(eventCallbacksMutex);
if (!initialized.exchange(false)) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
}
terminating = true;
}
ClearEventCallbacks();
{
std::lock_guard lock(userMutex);
eventQueue.clear();
}
{
std::unique_lock lock(eventCallbacksMutex);
terminating = false;
}
std::lock_guard lock(userMutex);
eventQueue.clear();
return USER_SERVICE_OK;
}

Expand Down Expand Up @@ -1248,8 +1416,7 @@ int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdClockType(void) {
}

int APS5_VABI sceUserServiceTerminateForShellCore(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return sceUserServiceTerminate();
}

int APS5_VABI sceUserServiceSetParentalMorpheus(void) {
Expand Down Expand Up @@ -2280,9 +2447,8 @@ int APS5_VABI sceUserServiceGetTeamShowAboutTeam(void) {
return 0;
}

int APS5_VABI sceUserServiceGetEventVsh(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceUserServiceGetEventVsh(SceUserServiceEvent* event) {
return sceUserServiceGetEvent(event);
}

int APS5_VABI sceUserServiceGetA11yControllerBName(void) {
Expand Down Expand Up @@ -2370,9 +2536,32 @@ int APS5_VABI sceUserServiceSetVibrationStrength(void) {
return 0;
}

int APS5_VABI sceUserServiceUnregisterEventCallback(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceUserServiceUnregisterEventCallback(UserServiceEventCallback callback, void* userData) {
if (!initialized.load()) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
}
if (callback == nullptr) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
std::vector<std::shared_ptr<RegisteredEventCallback>> removed;
{
std::unique_lock lock(eventCallbacksMutex);
if (!initialized.load()) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
}
for (const auto& entry : eventCallbacks) {
if (entry->callback == callback && entry->userData == userData) {
std::lock_guard entryLock(entry->mutex);
entry->active = false;
removed.push_back(entry);
}
}
}
for (const auto& entry : removed) {
DeactivateAndWait(entry);
}
RemoveDrainedCallbacks();
return USER_SERVICE_OK;
}

int APS5_VABI sceUserServiceGetTopMenuNotificationFlag(void) {
Expand Down Expand Up @@ -2784,9 +2973,8 @@ int APS5_VABI sceUserServiceGetGlsFloatingMessage(void) {
return 0;
}

int APS5_VABI sceUserServiceInitializeForShellCore(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceUserServiceInitializeForShellCore(void*) {
return MarkInitialized();
}

int APS5_VABI sceUserServiceGetGlsBcTags(void) {
Expand Down Expand Up @@ -2949,9 +3137,28 @@ int APS5_VABI sceUserServiceSetHmuBrightness(void) {
return 0;
}

int APS5_VABI sceUserServiceRegisterEventCallback(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceUserServiceRegisterEventCallback(UserServiceEventCallback callback, void* userData) {
if (!initialized.load()) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
}
if (callback == nullptr) {
return USER_SERVICE_OK;
}
std::unique_lock lock(eventCallbacksMutex);
if (!initialized.load()) {
return USER_SERVICE_ERROR_NOT_INITIALIZED;
}
for (const auto& entry : eventCallbacks) {
std::lock_guard entryLock(entry->mutex);
if (entry->active && entry->callback == callback && entry->userData == userData) {
return USER_SERVICE_OK;
}
}
auto entry = std::make_shared<RegisteredEventCallback>();
entry->callback = callback;
entry->userData = userData;
eventCallbacks.push_back(std::move(entry));
return USER_SERVICE_OK;
}

int APS5_VABI sceUserServiceGetParentalMorpheus(void) {
Expand Down
Loading
Loading