#include "prx/libSceSaveData/SaveDataCore.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libSceNpCommon/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" #include "prx/libSceRtc/include/Rtc.hpp" #include #include #include #include #include #include #include #include #include #include "SceTypes.hpp" #include "prx/libc/include/MallocManagedSize.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Pthread/include/Cond.hpp" #include "prx/libkernel/Pthread/include/Mutex.hpp" #include "prx/libkernel/Semaphore/include/Semaphore.hpp" static_assert(sizeof(NpCallout) == 0x28); static_assert(sizeof(NpCalloutContext) == 0x28); static_assert(sizeof(NpKernelMemory) == 0x18); static_assert(sizeof(NpMemoryHeap) == 0x20); namespace { constexpr int NP_ERROR_NULL_ARGUMENT = static_cast(0x80550003); constexpr int NP_ERROR_IPC_ALREADY_INITIALIZED = static_cast(0x80559D00); constexpr int NP_ERROR_IPC_NOT_INITIALIZED = static_cast(0x80559D01); constexpr int NP_ERROR_ONLINE_ID_MISMATCH = static_cast(0x80550609); constexpr int NP_ERROR_INVALID_NP_ID = static_cast(0x80550605); constexpr std::size_t NP_ID_OPT_OFFSET = 0x14; constexpr std::size_t NP_ID_PLATFORM_OFFSET = 0x18; constexpr std::size_t NP_ID_TERMINATOR_FLAG_OFFSET = 0x1C; std::uint32_t NpIdWord(const std::uint8_t* npId, std::size_t offset) { std::uint32_t value; std::memcpy(&value, npId + offset, sizeof(value)); return value; } constexpr std::size_t NP_ONLINE_ID_MAX = 16; constexpr std::size_t NP_ONLINE_ID_MIN = 3; constexpr int NP_ERROR_INVALID_ARGUMENT_UTIL = static_cast(0x80550601); constexpr std::size_t NP_LANGUAGE_CODE_SIZE = 6; constexpr std::size_t NP_LANGUAGE_CODE2_SIZE = 0x24; struct NpLanguageCodes { const char* code; const char* tag; }; constexpr NpLanguageCodes NP_LANGUAGE_CODES[] { {"ja", "ja-JP"}, {"en", "en-US"}, {"fr", "fr-FR"}, {"es", "es-ES"}, {"de", "de-DE"}, {"it", "it-IT"}, {"nl", "nl-NL"}, {"pt", "pt-PT"}, {"ru", "ru-RU"}, {"ko", "ko-KR"}, {"zh-TW", "zh-Hant"}, {"zh-CN", "zh-Hans"}, {"fi", "fi-FI"}, {"sv", "sv-SE"}, {"da", "da-DK"}, {"no", "no-NO"}, {"pl", "pl-PL"}, {"pt-BR", "pt-BR"}, {"en-GB", "en-GB"}, {"tr", "tr-TR"}, {"es-MX", "es-419"}, {"ar", "ar-AE"}, {"fr-CA", "fr-CA"}, {"cs", "cs-CZ"}, {"hu", "hu-HU"}, {"el", "el-GR"}, {"ro", "ro-RO"}, {"th", "th-TH"}, {"vi", "vi-VN"}, {"id", "id-ID"}, {"uk", "uk-UA"}, }; const NpLanguageCodes* FindLanguageCodes(int language) { if (language < 0 || static_cast(language) >= std::size(NP_LANGUAGE_CODES)) return nullptr; return &NP_LANGUAGE_CODES[language]; } void CopyText(char* out, const char* text, std::size_t size) { std::strncpy(out, text, size - 1); out[size - 1] = '\0'; } std::atomic g_debugIds {0}; constexpr int NP_UTIL_ERROR_BUFFER_TOO_SMALL = static_cast(0x80550602); constexpr int NP_UTIL_ERROR_OUT_OF_RANGE = static_cast(0x8055060A); constexpr std::size_t NP_TITLE_ID_SIZE = 13; constexpr std::uint8_t NP_TITLE_ID_INDEX_MAX = 99; constexpr std::size_t NP_DUMP_LINE_BYTES = 16; bool IsAsciiLetter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } bool IsOnlineIdText(const char* text) { const std::size_t length = std::strlen(text); if (length < NP_ONLINE_ID_MIN || length > NP_ONLINE_ID_MAX || !IsAsciiLetter(text[0])) return false; return std::all_of(text + 1, text + length, [](char c) { return IsAsciiLetter(c) || (c >= '0' && c <= '9') || c == '_' || c == '-'; }); } constexpr int NP_ERROR_BUFFER_TOO_SMALL = static_cast(0x80558008); constexpr std::size_t NP_SDK_VERSION_SIZE = 8; constexpr std::size_t NP_RFC3339_BUFFER_MIN = 0x80; constexpr std::size_t NP_USER_ID_LIST_SIZE = 20; constexpr int USER_ID_INVALID = -1; constexpr KernelClockid NP_SYSTEM_CLOCK = 4; static_assert(sizeof(SceKernelSystemSwVersion) == 0x28); std::uint32_t SystemVersion() { SceKernelSystemSwVersion version {}; version.size = sizeof(version); sceKernelGetSystemSwVersion_nid_postfix(&version); return version.version; } int BcdPair(std::uint32_t value, int shift) { return static_cast((value >> (shift + 4)) & 0xF) * 10 + static_cast((value >> shift) & 0xF); } constexpr int NP_ERROR_OUT_OF_MEMORY = static_cast(0x80550005); constexpr int NP_ERROR_NULL_OBJECT = static_cast(0x80558005); constexpr int NP_ERROR_HEAP_CREATE = static_cast(0x8055800A); constexpr std::uint64_t NP_KERNEL_MEMORY_ALIGNMENT = 0x4000; constexpr int NP_KERNEL_MEMORY_PROTECTION = 3; constexpr std::size_t NP_GLOBAL_HEAP_SIZE = 0x80000; constexpr char NP_GLOBAL_HEAP_NAME[] = "NpCommonGlobalHeap"; constexpr std::size_t NP_IPC_CLIENT_HEAP_SIZE = 0x10000; constexpr char NP_IPC_CLIENT_HEAP_NAME[] = "NpCommonIpcClientHeap"; void* APS5_VABI HeapAllocate(std::size_t size, void* context) { auto* heap = static_cast(context); return heap->mspace != nullptr ? sceLibcMspaceMalloc_nid_postfix(heap->mspace, size) : nullptr; } void* APS5_VABI HeapReallocate(void* pointer, std::size_t size, void* context) { auto* heap = static_cast(context); return heap->mspace != nullptr ? sceLibcMspaceRealloc_nid_postfix(heap->mspace, pointer, size) : nullptr; } void APS5_VABI HeapRelease(void* pointer, void* context) { auto* heap = static_cast(context); if (heap->mspace != nullptr && pointer != nullptr) sceLibcMspaceFree_nid_postfix(heap->mspace, pointer); } void* APS5_VABI MemoryHeapAllocate(std::size_t size, void* context) { return HeapAllocate(size, &static_cast(context)->heap); } void* APS5_VABI MemoryHeapReallocate(void* pointer, std::size_t size, void* context) { return HeapReallocate(pointer, size, &static_cast(context)->heap); } void APS5_VABI MemoryHeapRelease(void* pointer, void* context) { HeapRelease(pointer, &static_cast(context)->heap); } std::uint64_t AlignKernelMemory(std::uint64_t size) { const std::uint64_t remainder = size & (NP_KERNEL_MEMORY_ALIGNMENT - 1); return remainder == 0 ? size : size + NP_KERNEL_MEMORY_ALIGNMENT - remainder; } int MapKernelMemory(NpKernelMemory* memory, std::uint64_t size, const char* name) { memory->reserved = 0; memory->size = size; return sceKernelMapNamedFlexibleMemory(&memory->address, size, NP_KERNEL_MEMORY_PROTECTION, 0, name); } void UnmapKernelMemory(NpKernelMemory* memory) { if (memory->address != nullptr) sceKernelMunmap(reinterpret_cast(memory->address), memory->size); memory->address = nullptr; } int InitHeap(NpHeap* heap, void* base, std::size_t size, const char* name) { heap->mspace = sceLibcMspaceCreate_nid_postfix(name, base, size, 0); return heap->mspace != nullptr ? 0 : NP_ERROR_HEAP_CREATE; } void DestroyHeap(NpHeap* heap) { if (heap->mspace == nullptr) return; sceLibcMspaceDestroy_nid_postfix(heap->mspace); heap->mspace = nullptr; } int InitMemoryHeap(NpMemoryHeap* heap, std::uint64_t size, const char* name) { if (heap == nullptr) return NP_ERROR_NULL_OBJECT; int result = MapKernelMemory(&heap->memory, AlignKernelMemory(size), name); if (result < 0) return result; result = InitHeap(&heap->heap, heap->memory.address, heap->memory.size, name); if (result >= 0) return result; UnmapKernelMemory(&heap->memory); *heap = {}; return result; } void DestroyMemoryHeap(NpMemoryHeap* heap) { DestroyHeap(&heap->heap); UnmapKernelMemory(&heap->memory); } NpAllocator MemoryHeapAllocator(NpMemoryHeap* heap) { return {MemoryHeapAllocate, MemoryHeapReallocate, MemoryHeapRelease, heap}; } constexpr int NP_ERROR_NOT_INITIALIZED = static_cast(0x80558001); constexpr int NP_ERROR_ALREADY_INITIALIZED = static_cast(0x80558002); constexpr int NP_ERROR_ALREADY_QUEUED = static_cast(0x80558006); constexpr std::uint64_t NP_CALLOUT_MAX_WAIT = 0xFFFFFFFF; std::mutex g_randomMutex; std::mt19937 g_random {std::random_device {}()}; NpMemoryHeap g_globalHeap {}; NpAllocator g_globalAllocator {}; NpAllocatorEx g_globalAllocatorEx {}; int g_archReferences = 0; std::mutex g_archMutex; std::mutex g_ipcClientMutex; bool g_ipcClientInitialized = false; NpMemoryHeap g_ipcClientHeap {}; constexpr int KERNEL_ERROR_EBUSY = static_cast(0x80020010); constexpr int KERNEL_ERROR_ETIMEDOUT = static_cast(0x8002003C); constexpr int NP_ERROR_INVALID_ARGUMENT = static_cast(0x80558009); constexpr int NP_ERROR_TIMEOUT = static_cast(0x8055800B); constexpr int NP_ERROR_EVENT_FLAG_BUSY = static_cast(0x8055800C); constexpr int NP_ERROR_SEMA_BUSY = static_cast(0x8055800D); constexpr int NP_ERROR_MUTEX_BUSY = static_cast(0x8055800F); constexpr std::uint32_t NP_MUTEX_FLAG_RECURSIVE = 1; constexpr int PTHREAD_MUTEX_TYPE_RECURSIVE = 2; constexpr std::uint32_t NP_SEMA_FLAG_PRIORITY = 1; constexpr std::uint32_t KERNEL_SEMA_ATTR_FIFO = 1; constexpr std::uint32_t KERNEL_SEMA_ATTR_PRIORITY_SHIFT = 8; constexpr int NP_THREAD_EXPLICIT_SCHED = 0; constexpr int NP_THREAD_SCHED_POLICY = 1; int NpResult(int result) { return result < 0 ? result : 0; } int NpBusyResult(int result, int busy) { return result == KERNEL_ERROR_EBUSY ? busy : NpResult(result); } int InitMutex(PthreadMutex* mutex, const char* name, std::uint32_t flags) { PthreadMutexattr attr {}; int result = scePthreadMutexattrInit(&attr); if (result < 0) return result; if ((flags & NP_MUTEX_FLAG_RECURSIVE) != 0) result = scePthreadMutexattrSettype(&attr, PTHREAD_MUTEX_TYPE_RECURSIVE); if (result >= 0) result = scePthreadMutexInit(mutex, &attr, name); scePthreadMutexattrDestroy(&attr); return NpResult(result); } int InitCond(PthreadCond* cond, const char* name) { PthreadCondattr attr {}; const int result = scePthreadCondattrInit(&attr); if (result < 0) return result; const int created = scePthreadCondInit(cond, &attr, name); scePthreadCondattrDestroy(&attr); return created; } } extern "C" { int APS5_VABI sceNpAtomicCas32(std::int32_t* value, std::int32_t expected, std::int32_t desired) { std::atomic_ref(*value).compare_exchange_strong(expected, desired); return expected; } int APS5_VABI sceNpAtomicInc32(std::int32_t* value) { return std::atomic_ref(*value).fetch_add(1); } int APS5_VABI sceNpAtomicDec32(std::int32_t* value) { return std::atomic_ref(*value).fetch_sub(1); } int APS5_VABI sceNpCondSignalAll(PthreadCond* cond) { return scePthreadCondBroadcast(cond); } int APS5_VABI sceNpCondSignalTo(PthreadCond* cond, Pthread thread) { return scePthreadCondSignalto(cond, thread); } int APS5_VABI sceNpCondWait(PthreadCond* cond, PthreadMutex* mutex) { return scePthreadCondWait(cond, mutex); } int APS5_VABI sceNpLwCondInit(PthreadCond* cond, const char* name) { return InitCond(cond, name); } int APS5_VABI sceNpLwCondDestroy(PthreadCond* cond) { return scePthreadCondDestroy(cond); } int APS5_VABI sceNpLwCondSignal(PthreadCond* cond) { return scePthreadCondSignal(cond); } int APS5_VABI sceNpLwCondSignalAll(PthreadCond* cond) { return scePthreadCondBroadcast(cond); } int APS5_VABI sceNpLwCondSignalTo(PthreadCond* cond, Pthread thread) { return scePthreadCondSignalto(cond, thread); } int APS5_VABI sceNpLwCondWait(PthreadCond* cond, PthreadMutex* mutex) { return scePthreadCondWait(cond, mutex); } int APS5_VABI sceNpCreateEventFlag(KernelEventFlag* flag, const char* name, std::uint32_t attr, std::uint64_t pattern) { return NpResult(sceKernelCreateEventFlag(flag, name, attr, pattern, nullptr)); } int APS5_VABI sceNpDeleteEventFlag(KernelEventFlag flag) { return NpResult(sceKernelDeleteEventFlag(flag)); } int APS5_VABI sceNpOpenEventFlag(KernelEventFlag* flag, const char* name) { return NpResult(sceKernelOpenEventFlag(flag, name)); } int APS5_VABI sceNpCloseEventFlag(KernelEventFlag flag) { return NpResult(sceKernelCloseEventFlag(flag)); } int APS5_VABI sceNpSetEventFlag(KernelEventFlag flag, std::uint64_t pattern) { return NpResult(sceKernelSetEventFlag(flag, pattern)); } int APS5_VABI sceNpClearEventFlag(KernelEventFlag flag, std::uint64_t pattern) { return NpResult(sceKernelClearEventFlag(flag, pattern)); } int APS5_VABI sceNpPollEventFlag(KernelEventFlag flag, std::uint64_t pattern, std::uint32_t mode, std::uint64_t* result) { return NpBusyResult(sceKernelPollEventFlag(flag, pattern, mode, result), NP_ERROR_EVENT_FLAG_BUSY); } int APS5_VABI sceNpCancelEventFlag(KernelEventFlag flag, std::uint64_t pattern, int* waiting) { return NpResult(sceKernelCancelEventFlag(flag, pattern, waiting)); } int APS5_VABI sceNpCreateSema(KernelSema* sema, std::uint32_t flags, int initial, int maximum, const char* name) { const std::uint32_t attr = KERNEL_SEMA_ATTR_FIFO + ((flags & NP_SEMA_FLAG_PRIORITY) << KERNEL_SEMA_ATTR_PRIORITY_SHIFT); return NpResult(sceKernelCreateSema(sema, name, attr, initial, maximum, nullptr)); } int APS5_VABI sceNpDeleteSema(KernelSema sema) { return NpResult(sceKernelDeleteSema(sema)); } int APS5_VABI sceNpPollSema(KernelSema sema) { return NpBusyResult(sceKernelPollSema(sema, 1), NP_ERROR_SEMA_BUSY); } int APS5_VABI sceNpSignalSema(KernelSema sema) { return NpResult(sceKernelSignalSema(sema, 1)); } int APS5_VABI sceNpCreateThread(Pthread* thread, PthreadEntry entry, void* arg, int priority, std::size_t stackSize, KernelCpumask affinity, const char* name) { PthreadAttr attr {}; int result = scePthreadAttrInit(&attr); if (result < 0) return result; result = scePthreadAttrSetstacksize(&attr, stackSize); if (result < 0) { scePthreadAttrDestroy(&attr); return result; } if (priority != 0) { result = scePthreadAttrSetinheritsched(&attr, NP_THREAD_EXPLICIT_SCHED); if (result >= 0) result = scePthreadAttrSetschedpolicy(&attr, NP_THREAD_SCHED_POLICY); if (result >= 0) { KernelSchedParam param {}; param.sched_priority = priority; result = scePthreadAttrSetschedparam(&attr, ¶m); } } if (result >= 0 && affinity != 0) result = scePthreadAttrSetaffinity(&attr, affinity); if (result >= 0) result = scePthreadCreate(thread, &attr, entry, arg, name); scePthreadAttrDestroy(&attr); return result; } int APS5_VABI sceNpThreadGetId(Pthread* thread) { if (thread == nullptr) return NP_ERROR_INVALID_ARGUMENT; *thread = scePthreadSelf(); return 0; } int APS5_VABI sceNpGetProcessId(void) { return getpid_nid_postfix(); } [[noreturn]] void APS5_VABI sceNpPanic(void) { std::abort(); } int APS5_VABI sceNpDbgDumpText(const char* text, int length) { return std::printf("%.*s", length, text); } int APS5_VABI sceNpIpc2ClientInit(void) { std::lock_guard lock(g_ipcClientMutex); if (g_ipcClientInitialized) return NP_ERROR_IPC_ALREADY_INITIALIZED; const int result = InitMemoryHeap(&g_ipcClientHeap, NP_IPC_CLIENT_HEAP_SIZE, NP_IPC_CLIENT_HEAP_NAME); if (result < 0) return result; g_ipcClientInitialized = true; return 0; } int APS5_VABI sceNpIpc2ClientTerm(void) { std::lock_guard lock(g_ipcClientMutex); if (!g_ipcClientInitialized) return NP_ERROR_IPC_NOT_INITIALIZED; DestroyMemoryHeap(&g_ipcClientHeap); g_ipcClientInitialized = false; return 0; } int APS5_VABI sceNpArchInit(void) { std::lock_guard lock(g_archMutex); if (g_archReferences != 0) { ++g_archReferences; return 0; } const int result = InitMemoryHeap(&g_globalHeap, NP_GLOBAL_HEAP_SIZE, NP_GLOBAL_HEAP_NAME); if (result < 0) return result; g_archReferences = 1; return 0; } void APS5_VABI sceNpArchTerm(void) { std::lock_guard lock(g_archMutex); if (g_archReferences == 0) return; if (--g_archReferences == 0) DestroyMemoryHeap(&g_globalHeap); } int APS5_VABI sceNpHeapInit(NpHeap* heap, void* base, std::size_t size, const char* name) { return InitHeap(heap, base, size, name); } void APS5_VABI sceNpHeapDestroy(NpHeap* heap) { DestroyHeap(heap); } void APS5_VABI sceNpHeapGetAllocator(NpHeap* heap, NpAllocator* allocator) { *allocator = {HeapAllocate, HeapReallocate, HeapRelease, heap}; } int APS5_VABI sceNpHeapGetStat(const NpHeap* heap, NpHeapStat* stat) { MallocManagedSize stats {}; stats.size = sizeof(stats); stats.version = 1; const int result = sceLibcMspaceMallocStats_nid_postfix(heap->mspace, &stats); if (result == 0) *stat = {stats.maxSystemSize, stats.maxInuseSize, stats.currentInuseSize}; return result; } void APS5_VABI sceNpHeapShowStat(const NpHeap* heap) { NpHeapStat stat {}; if (sceNpHeapGetStat(heap, &stat) != 0) { std::printf("[NpCommon] heap statistics unavailable\n"); return; } std::printf("[NpCommon] heap: system %llu bytes, peak in use %llu bytes, in use %llu bytes\n", static_cast(stat.maxSystemSize), static_cast(stat.maxInuseSize), static_cast(stat.currentInuseSize)); } int APS5_VABI sceNpMemoryHeapInit(NpMemoryHeap* heap, std::uint64_t size, const char* name) { return InitMemoryHeap(heap, size, name); } void APS5_VABI sceNpMemoryHeapDestroy(NpMemoryHeap* heap) { DestroyMemoryHeap(heap); } void APS5_VABI sceNpMemoryHeapGetAllocator(NpMemoryHeap* heap, NpAllocator* allocator) { *allocator = MemoryHeapAllocator(heap); } void APS5_VABI sceNpMemoryHeapGetAllocatorEx(NpMemoryHeap* heap, int outOfMemoryError, NpAllocatorEx* allocator) { *allocator = {MemoryHeapAllocator(heap), outOfMemoryError}; } void APS5_VABI sceNpGlobalHeapGetAllocator(NpAllocator* allocator) { *allocator = MemoryHeapAllocator(&g_globalHeap); } void APS5_VABI sceNpGlobalHeapGetAllocatorEx(NpAllocatorEx* allocator) { *allocator = {MemoryHeapAllocator(&g_globalHeap), NP_ERROR_OUT_OF_MEMORY}; } NpAllocator* APS5_VABI sceNpGlobalHeapGetAllocatorPtr(void) { g_globalAllocator = MemoryHeapAllocator(&g_globalHeap); return &g_globalAllocator; } NpAllocatorEx* APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void) { g_globalAllocatorEx = {MemoryHeapAllocator(&g_globalHeap), NP_ERROR_OUT_OF_MEMORY}; return &g_globalAllocatorEx; } int APS5_VABI sceNpAllocateKernelMemoryNoAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory) { if (memory == nullptr) return NP_ERROR_NULL_OBJECT; const int result = MapKernelMemory(memory, size, name); if (result >= 0 && address != nullptr) *address = memory->address; return result; } int APS5_VABI sceNpAllocateKernelMemoryWithAlignment(std::uint64_t size, const char* name, void** address, NpKernelMemory* memory) { return sceNpAllocateKernelMemoryNoAlignment(AlignKernelMemory(size), name, address, memory); } int APS5_VABI sceNpFreeKernelMemory(NpKernelMemory* memory) { if (memory == nullptr) return NP_ERROR_NULL_OBJECT; UnmapKernelMemory(memory); return 0; } void APS5_VABI sceNpGetSdkVersionUInt(std::uint32_t* version) { const std::uint32_t value = SystemVersion(); if (version != nullptr) *version = value; } int APS5_VABI sceNpRtcConvertToPosixTime(const RtcTick* tick, std::int64_t* seconds) { if (tick->tick == 0) return NP_ERROR_INVALID_ARGUMENT; RtcDateTime time {}; const int result = sceRtcSetTick(&time, tick); if (result < 0) return result; return NpResult(sceRtcGetTime_t(&time, seconds)); } int APS5_VABI sceNpRtcFormatRFC3339(char* text, std::size_t size, const RtcTick* utc, int timeZoneMinutes) { if (size < NP_RFC3339_BUFFER_MIN) return NP_ERROR_BUFFER_TOO_SMALL; const int result = sceRtcFormatRFC3339(text, utc, timeZoneMinutes); if (result < 0) return result; text[size - 1] = 0; return 0; } int APS5_VABI sceNpUserGetUserIdList(int* users, std::size_t capacity, int* count) { if (capacity < NP_USER_ID_LIST_SIZE) return NP_ERROR_INVALID_ARGUMENT; int registered[NP_USER_ID_LIST_SIZE]; std::fill(std::begin(registered), std::end(registered), USER_ID_INVALID); static_assert(sizeof(UserServiceRegisteredUserIdList) <= sizeof(registered)); const int result = sceUserServiceGetRegisteredUserIdList(reinterpret_cast(registered)); if (result < 0) return result; int found = 0; for (const int user : registered) if (user != USER_ID_INVALID) users[found++] = user; *count = found; return 0; } int APS5_VABI sceNpRtcParseRFC3339(RtcTick* utc, const char* text) { return sceRtcParseRFC3339(utc, text); } static std::uint64_t ClockUsec() { std::int64_t usec = 0; sceNpGetSystemClockUsec(&usec); return static_cast(usec); } static void* APS5_VABI CalloutThread(void* arg) { auto* context = static_cast(arg); sceNpMutexLock(&context->mutex); for (;;) { const std::uint64_t now = ClockUsec(); NpCallout* head = context->pending; if (head != nullptr && head->deadline <= now) { context->pending = head->next; sceNpMutexUnlock(&context->mutex); head->handler(head->arg); sceNpMutexLock(&context->mutex); continue; } if (context->stopping > 0) break; const std::uint64_t wait = head != nullptr ? std::min(head->deadline - now, NP_CALLOUT_MAX_WAIT) : 0; sceNpCondTimedwait(&context->cond, &context->mutex, static_cast(wait)); if (context->stopping > 0) break; } sceNpMutexUnlock(&context->mutex); return nullptr; } int APS5_VABI sceNpCalloutInitCtx(NpCalloutContext* context, const char* name, std::size_t stackSize, int priority, KernelCpumask affinity) { if (context->initialized > 0) return NP_ERROR_ALREADY_INITIALIZED; context->stopping = 0; context->pending = nullptr; int result = sceNpMutexInit(&context->mutex, name, 1); if (result < 0) return result; result = sceNpCondInit(&context->cond, name, 0); if (result >= 0) { result = sceNpCreateThread(&context->thread, CalloutThread, context, priority, stackSize, affinity, name); if (result >= 0) { context->initialized = 1; return 0; } sceNpCondDestroy(&context->cond); } sceNpMutexDestroy(&context->mutex); return result; } void APS5_VABI sceNpCalloutTermCtx(NpCalloutContext* context) { if (context->initialized == 0) return; sceNpMutexLock(&context->mutex); context->stopping = 1; sceNpCondSignal(&context->cond); sceNpMutexUnlock(&context->mutex); sceNpJoinThread(context->thread, nullptr); sceNpCondDestroy(&context->cond); sceNpMutexDestroy(&context->mutex); context->initialized = 0; } int APS5_VABI sceNpCalloutStartOnCtx64(NpCalloutContext* context, NpCallout* callout, std::uint64_t usec, void (APS5_VABI *handler)(void* arg), void* arg) { if (context->initialized == 0) return NP_ERROR_NOT_INITIALIZED; const std::uint64_t deadline = ClockUsec() + usec; sceNpMutexLock(&context->mutex); for (NpCallout* entry = context->pending; entry != nullptr; entry = entry->next) { if (entry == callout) { sceNpMutexUnlock(&context->mutex); return NP_ERROR_ALREADY_QUEUED; } } *callout = {}; callout->deadline = deadline; callout->handler = handler; callout->arg = arg; NpCallout** link = &context->pending; while (*link != nullptr && callout->deadline > (*link)->deadline) link = &(*link)->next; callout->next = *link; *link = callout; sceNpCondSignal(&context->cond); sceNpMutexUnlock(&context->mutex); return 0; } int APS5_VABI sceNpCalloutStartOnCtx(NpCalloutContext* context, NpCallout* callout, std::uint32_t usec, void (APS5_VABI *handler)(void* arg), void* arg) { return sceNpCalloutStartOnCtx64(context, callout, usec, handler, arg); } int APS5_VABI sceNpCalloutStopOnCtx(NpCalloutContext* context, NpCallout* callout, int* wasPending) { if (context->initialized == 0) return NP_ERROR_NOT_INITIALIZED; sceNpMutexLock(&context->mutex); int removed = 0; for (NpCallout** link = &context->pending; *link != nullptr; link = &(*link)->next) { if (*link != callout) continue; *link = callout->next; removed = 1; break; } if (wasPending != nullptr) *wasPending = removed; sceNpMutexUnlock(&context->mutex); return 0; } std::uint32_t APS5_VABI sceNpGetRandom(void) { std::lock_guard lock(g_randomMutex); return static_cast(g_random()); } int APS5_VABI sceNpUtilCmpAccountId(std::uint64_t left, std::uint64_t right) { return left != right ? 1 : 0; } int APS5_VABI sceNpCmpNpId(const void* npId1, const void* npId2) { if (!npId1 || !npId2) return NP_ERROR_NULL_ARGUMENT; const auto* left = static_cast(npId1); const auto* right = static_cast(npId2); if (left[NP_ID_TERMINATOR_FLAG_OFFSET] != 1 || right[NP_ID_TERMINATOR_FLAG_OFFSET] != 1) return NP_ERROR_INVALID_NP_ID; if (std::strncmp(static_cast(npId1), static_cast(npId2), NP_ONLINE_ID_MAX) != 0) return NP_ERROR_ONLINE_ID_MISMATCH; if (NpIdWord(left, NP_ID_OPT_OFFSET) != NpIdWord(right, NP_ID_OPT_OFFSET)) return NP_ERROR_ONLINE_ID_MISMATCH; if (NpIdWord(left, NP_ID_PLATFORM_OFFSET) == NpIdWord(right, NP_ID_PLATFORM_OFFSET)) return 0; if (left[NP_ID_PLATFORM_OFFSET] == 0 || right[NP_ID_PLATFORM_OFFSET] == 0) return 0; return NP_ERROR_ONLINE_ID_MISMATCH; } bool APS5_VABI sceNpIntIsOnlineIdString(const char* text) { return text != nullptr && IsOnlineIdText(text); } int APS5_VABI sceNpUtilGetNpLanguageCode(int language, char* code) { const NpLanguageCodes* codes = FindLanguageCodes(language); if (codes == nullptr || code == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; CopyText(code, codes->code, NP_LANGUAGE_CODE_SIZE); return 0; } int APS5_VABI sceNpUtilGetNpLanguageCode2(int language, char* tag) { const NpLanguageCodes* codes = FindLanguageCodes(language); if (codes == nullptr || tag == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; CopyText(tag, codes->tag, NP_LANGUAGE_CODE2_SIZE); return 0; } const char* APS5_VABI sceNpUtilGetNpLanguageCodeStr(int language) { const NpLanguageCodes* codes = FindLanguageCodes(language); return codes != nullptr ? codes->code : nullptr; } const char* APS5_VABI sceNpUtilGetNpLanguageCode2Str(int language) { const NpLanguageCodes* codes = FindLanguageCodes(language); return codes != nullptr ? codes->tag : nullptr; } std::uint32_t APS5_VABI sceNpDbgAssignDebugId(void) { std::uint32_t id = g_debugIds.fetch_add(1) + 1; if (id == 0) id = g_debugIds.fetch_add(1) + 1; return id; } int APS5_VABI sceNpUtilBuildTitleId(const NpTitleIdParts* parts, char* titleId, std::size_t size) { if (parts == nullptr || titleId == nullptr) return NP_ERROR_INVALID_ARGUMENT_UTIL; if (size < NP_TITLE_ID_SIZE) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; if (parts->index > NP_TITLE_ID_INDEX_MAX) return NP_UTIL_ERROR_OUT_OF_RANGE; std::memcpy(titleId, parts->id, sizeof(parts->id)); titleId[9] = '_'; titleId[10] = static_cast('0' + parts->index / 10); titleId[11] = static_cast('0' + parts->index % 10); titleId[12] = '\0'; return 0; } void APS5_VABI sceNpDbgDumpBinary(const void* data, std::size_t size) { const auto* bytes = static_cast(data); for (std::size_t line = 0; line < size; line += NP_DUMP_LINE_BYTES) { const std::size_t count = std::min(NP_DUMP_LINE_BYTES, size - line); for (std::size_t i = 0; i < NP_DUMP_LINE_BYTES; ++i) { if (i < count) { std::printf("%02x", bytes[line + i]); } else { std::printf(" "); } if ((i & 1) != 0) std::putchar(' '); } std::putchar(' '); for (std::size_t i = 0; i < count; ++i) { const std::uint8_t c = bytes[line + i]; std::putchar(c >= 0x20 && c <= 0x7E ? c : '.'); } std::putchar('\n'); } std::putchar('\n'); } }