#include "prx/libkernel/include/Api.hpp" #include "prx/libSceNpCommon/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include #include #include #include #include static constexpr int INVALID_ARGUMENT = static_cast(0x80550003); static constexpr int INVALID_PLATFORM_TYPE = static_cast(0x80550004); static constexpr int NOT_MATCH = static_cast(0x80550609); static constexpr int COND_TIMEDOUT = static_cast(0x8055800B); static constexpr int MUTEX_BUSY = static_cast(0x8055800F); static bool expect(bool condition, const char* message) { if (!condition) std::fprintf(stderr, "%s\n", message); return condition; } static NpOnlineId onlineId(const char* text) { NpOnlineId id{}; std::strncpy(id.data, text, sizeof(id.data)); return id; } static NpIdFull npIdWithTag(const char* handle, std::uint32_t tag) { NpIdFull id{}; id.handle = onlineId(handle); std::memcpy(id.opt + 4, &tag, sizeof(tag)); return id; } static int testIds() { const NpOnlineId kestrel = onlineId("kestrel_77"); const NpOnlineId kestrelCopy = onlineId("kestrel_77"); const NpOnlineId heron = onlineId("heron-4"); bool ok = expect(sceNpCmpOnlineId(nullptr, &kestrel) == INVALID_ARGUMENT, "a null online ID was compared"); ok &= expect(sceNpCmpOnlineId(&kestrel, nullptr) == INVALID_ARGUMENT, "a null second online ID was compared"); ok &= expect(sceNpCmpOnlineId(nullptr, nullptr) == INVALID_ARGUMENT, "null online IDs were compared"); ok &= expect(sceNpCmpOnlineId(&kestrel, &kestrelCopy) == 0, "equal online IDs did not match"); ok &= expect(sceNpCmpOnlineId(&kestrel, &heron) == NOT_MATCH, "different online IDs matched"); NpIdFull low = npIdWithTag("kestrel_77", 0x00347370); NpIdFull high = low; high.reserved[7] = 1; std::int32_t order = 5; ok &= expect(sceNpCmpNpIdInOrder(&low, &high, nullptr) == INVALID_ARGUMENT, "a null order was accepted"); ok &= expect(sceNpCmpNpIdInOrder(nullptr, &high, &order) == INVALID_ARGUMENT, "a null first NpId was accepted"); ok &= expect(sceNpCmpNpIdInOrder(&low, nullptr, &order) == INVALID_ARGUMENT, "a null second NpId was accepted"); ok &= expect(sceNpCmpNpIdInOrder(nullptr, nullptr, &order) == INVALID_ARGUMENT, "null NpIds were accepted"); ok &= expect(sceNpCmpNpIdInOrder(nullptr, nullptr, nullptr) == INVALID_ARGUMENT, "all null arguments were accepted"); ok &= expect(sceNpCmpNpIdInOrder(&low, &high, &order) == 0 && order == -1, "reserved bytes did not order the NpIds"); ok &= expect(sceNpCmpNpIdInOrder(&high, &low, &order) == 0 && order == 1, "the reverse order is not 1"); ok &= expect(sceNpCmpNpIdInOrder(&low, &low, &order) == 0 && order == 0, "an NpId is not equal to itself"); high = low; high.opt[0] = 9; high.reserved[0] = 0; low.reserved[0] = 200; ok &= expect(sceNpCmpNpIdInOrder(&low, &high, &order) == 0 && order == -1, "opt did not take precedence over reserved"); const NpIdFull heronId = npIdWithTag("heron-4", 0); ok &= expect(sceNpCmpNpIdInOrder(&heronId, &low, &order) == 0 && order == -1, "the handle did not take precedence"); const NpIdFull none = npIdWithTag("heron-4", 0); const NpIdFull ps3 = npIdWithTag("heron-4", 0x00337370); const NpIdFull vita = npIdWithTag("heron-4", 0x32707370); const NpIdFull ps4 = npIdWithTag("heron-4", 0x00347370); const NpIdFull other = npIdWithTag("heron-4", 0x00357370); ok &= expect(sceNpGetPlatformType(nullptr) == INVALID_ARGUMENT, "a null NpId has a platform"); ok &= expect(sceNpGetPlatformType(&none) == 0, "an untagged NpId is not platform 0"); ok &= expect(sceNpGetPlatformType(&ps3) == 1, "the ps3 tag is not platform 1"); ok &= expect(sceNpGetPlatformType(&vita) == 2, "the psp2 tag is not platform 2"); ok &= expect(sceNpGetPlatformType(&ps4) == 3, "the ps4 tag is not platform 3"); ok &= expect(sceNpGetPlatformType(&other) == INVALID_PLATFORM_TYPE, "an unknown tag was accepted"); NpOnlineId unterminated = onlineId("abcdefghijklmnop"); unterminated.data[16] = 'q'; ok &= expect(sceNpIntIsValidOnlineId(nullptr) == 0, "a null online ID is valid"); ok &= expect(sceNpIntIsValidOnlineId(&kestrel) == 1, "kestrel_77 is not valid"); ok &= expect(sceNpIntIsValidOnlineId(&heron) == 1, "heron-4 is not valid"); const NpOnlineId sixteen = onlineId("abcdefghijklmnop"); const NpOnlineId shortId = onlineId("ab"); const NpOnlineId leadingDash = onlineId("-heron"); const NpOnlineId dotted = onlineId("her.on"); ok &= expect(sceNpIntIsValidOnlineId(&sixteen) == 1, "a 16 character ID is not valid"); ok &= expect(sceNpIntIsValidOnlineId(&unterminated) == 0, "an unterminated ID is valid"); ok &= expect(sceNpIntIsValidOnlineId(&shortId) == 0, "a 2 character ID is valid"); ok &= expect(sceNpIntIsValidOnlineId(&leadingDash) == 0, "an ID starting with '-' is valid"); ok &= expect(sceNpIntIsValidOnlineId(&dotted) == 0, "an ID with '.' is valid"); return ok ? 0 : 1; } static void* APS5_VABI returnTicket(void* arg) { return reinterpret_cast(reinterpret_cast(arg) + 1); } struct Wakeup { PthreadCond cond = nullptr; PthreadMutex mutex = nullptr; bool lightweight = false; int waiting = 0; int woken = 0; bool ready = false; }; static void* APS5_VABI waitUntilReady(void* arg) { auto* wakeup = static_cast(arg); sceNpMutexLock(&wakeup->mutex); ++wakeup->waiting; int result = 0; while (!wakeup->ready && result == 0) result = wakeup->lightweight ? sceNpLwCondWait(&wakeup->cond, &wakeup->mutex) : sceNpCondWait(&wakeup->cond, &wakeup->mutex); if (result == 0) ++wakeup->woken; sceNpMutexUnlock(&wakeup->mutex); return nullptr; } enum class Wake { One, All, To }; static bool wakeWaiters(bool lightweight, int count, Wake wake) { Wakeup wakeup; wakeup.lightweight = lightweight; bool ok = expect(sceNpMutexInit(&wakeup.mutex, "np-wake", 0) == 0, "the wake mutex was not created"); ok &= expect((lightweight ? sceNpLwCondInit(&wakeup.cond, "np-lw-wake") : sceNpCondInit(&wakeup.cond, "np-wake", 0)) == 0 && wakeup.cond, "the wake condition was not created"); if (!ok) return false; Pthread threads[2] = {}; for (int index = 0; index < count; ++index) if (!expect(scePthreadCreate(&threads[index], nullptr, waitUntilReady, &wakeup, "np-waiter") == 0, "a waiter was not created")) std::abort(); for (;;) { sceNpMutexLock(&wakeup.mutex); const bool waiting = wakeup.waiting == count; if (waiting) break; sceNpMutexUnlock(&wakeup.mutex); scePthreadYield(); } wakeup.ready = true; int signalled = 0; if (wake == Wake::All) signalled = lightweight ? sceNpLwCondSignalAll(&wakeup.cond) : sceNpCondSignalAll(&wakeup.cond); else if (wake == Wake::To) signalled = lightweight ? sceNpLwCondSignalTo(&wakeup.cond, threads[0]) : sceNpCondSignalTo(&wakeup.cond, threads[0]); else signalled = lightweight ? sceNpLwCondSignal(&wakeup.cond) : sceNpCondSignal(&wakeup.cond); sceNpMutexUnlock(&wakeup.mutex); if (!expect(signalled == 0, "signalling the waiters failed")) std::abort(); for (int index = 0; index < count; ++index) ok &= expect(scePthreadJoin(threads[index], nullptr) == 0, "a waiter was not joined"); ok &= expect(wakeup.woken == count, "not every waiter returned from its wait"); ok &= expect((lightweight ? sceNpLwCondDestroy(&wakeup.cond) : sceNpCondDestroy(&wakeup.cond)) == 0, "the wake condition was not destroyed"); ok &= expect(sceNpMutexDestroy(&wakeup.mutex) == 0, "the wake mutex was not destroyed"); return ok; } static int testWake() { bool ok = true; for (const bool lightweight : {false, true}) { ok &= expect(wakeWaiters(lightweight, 1, Wake::One), lightweight ? "sceNpLwCondSignal did not wake its waiter" : "sceNpCondSignal did not wake its waiter"); ok &= expect(wakeWaiters(lightweight, 2, Wake::All), lightweight ? "sceNpLwCondSignalAll did not wake both waiters" : "sceNpCondSignalAll did not wake both waiters"); ok &= expect(wakeWaiters(lightweight, 1, Wake::To), lightweight ? "sceNpLwCondSignalTo did not wake its thread" : "sceNpCondSignalTo did not wake its thread"); } return ok ? 0 : 1; } static int testSync() { PthreadMutex plain = nullptr; PthreadMutex recursive = nullptr; bool ok = expect(sceNpMutexInit(&plain, "np-plain", 0) == 0 && plain, "a plain mutex was not created"); ok &= expect(sceNpLwMutexInit(&recursive, "np-recursive", 1) == 0 && recursive, "a recursive mutex was not created"); if (!ok) return 1; ok &= expect(sceNpMutexLock(&plain) == 0, "the plain mutex did not lock"); ok &= expect(sceNpMutexTryLock(&plain) == MUTEX_BUSY, "a held plain mutex did not report BUSY"); ok &= expect(sceNpMutexUnlock(&plain) == 0, "the plain mutex did not unlock"); ok &= expect(sceNpLwMutexTryLock(&recursive) == 0 && sceNpLwMutexTryLock(&recursive) == 0, "flag 1 did not make the mutex recursive"); ok &= expect(sceNpLwMutexUnlock(&recursive) == 0 && sceNpLwMutexUnlock(&recursive) == 0, "the recursive mutex did not unlock twice"); ok &= expect(sceNpLwMutexLock(&recursive) == 0, "the recursive mutex did not lock via sceNpLwMutexLock"); ok &= expect(sceNpLwMutexUnlock(&recursive) == 0, "the recursive mutex did not unlock"); ok &= expect(sceNpLwMutexDestroy(&recursive) == 0, "the recursive mutex was not destroyed"); PthreadCond cond = nullptr; ok &= expect(sceNpCondInit(&cond, "np-cond", 0) == 0 && cond, "a condition was not created"); ok &= expect(sceNpMutexLock(&plain) == 0, "the plain mutex did not lock for the wait"); ok &= expect(sceNpCondTimedwait(&cond, &plain, 2000) == COND_TIMEDOUT, "a timed wait did not report TIMEDOUT"); ok &= expect(sceNpMutexUnlock(&plain) == 0, "the mutex was not held after the wait"); ok &= expect(sceNpCondSignal(&cond) == 0, "signalling without waiters failed"); ok &= expect(sceNpCondDestroy(&cond) == 0, "the condition was not destroyed"); ok &= expect(sceNpMutexDestroy(&plain) == 0, "the plain mutex was not destroyed"); Pthread thread = nullptr; void* retval = nullptr; ok &= expect(scePthreadCreate(&thread, nullptr, returnTicket, reinterpret_cast(0x4140), "np-join") == 0, "the thread was not created"); ok &= expect(sceNpJoinThread(thread, &retval) == 0 && retval == reinterpret_cast(0x4141), "the join did not return the thread result"); return ok ? 0 : 1; } static int testClock() { KernelTimespec before{}; KernelTimespec after{}; std::int64_t usec = -1; sceKernelClockGettime(4, &before); bool ok = expect(sceNpGetSystemClockUsec(&usec) == 0, "the clock was not read"); sceKernelClockGettime(4, &after); ok &= expect(sceNpGetSystemClockUsec(nullptr) == INVALID_ARGUMENT, "a null destination was not rejected"); std::int64_t later = -1; ok &= expect(sceNpGetSystemClockUsec(&later) == 0 && later >= usec && usec >= 0, "the clock went backwards"); ok &= expect(usec >= before.tv_sec * 1000000 + before.tv_nsec / 1000 && usec <= after.tv_sec * 1000000 + after.tv_nsec / 1000, "the clock is not the monotonic kernel clock in microseconds"); char version[12]; std::memset(version, 'x', sizeof(version)); sceNpGetSdkVersion(version); ok &= expect(std::memcmp(version, "1.00\0\0\0\0", 8) == 0, "the version is not 1.00 padded with zeros"); ok &= expect(version[8] == 'x', "the version was written past 8 bytes"); return ok ? 0 : 1; } int main(int argc, char** argv) { if (argc != 2) return 2; int status = 2; if (std::strcmp(argv[1], "ids") == 0) status = testIds(); else if (std::strcmp(argv[1], "sync") == 0) status = testSync(); else if (std::strcmp(argv[1], "clock") == 0) status = testClock(); else if (std::strcmp(argv[1], "wake") == 0) status = testWake(); if (status == 0) std::printf("NpCommon %s tests passed\n", argv[1]); return status; }