fix(libc): add missing Unity engine static imports

This commit is contained in:
hamb3r 2026-10-11 18:13:56 +03:00
parent bb48d60fc6
commit b8a1cbe42b
7 changed files with 260 additions and 41 deletions

View File

@ -218,16 +218,8 @@ static int RecordWriteAddressFromCounterPair(Apr::CommandBufferObject* buffer, v
return AppendCommand(buffer, Apr::Opcode::WriteAddressFromCounterPair, Apr::WriteAddressFromCounterCommand{{}, reinterpret_cast<std::uint64_t>(address), counter, counter + 1u});
}
struct Xtime {
std::int64_t sec;
std::int64_t nsec;
};
static constexpr int ThrdSuccess = 0;
static constexpr int ThrdError = 4;
static constexpr int GuestClockRealtime = 0;
static constexpr std::int64_t NanosPerSecond = 1000000000;
static constexpr std::int64_t LongestSleepSeconds = 86400;
static std::atomic_flag sharedPtrSpinLock;
@ -238,22 +230,6 @@ int APS5_VABI _Cnd_init_nid_postfix(void** handle);
int SignalCondition_nid_no_patch(void** handle);
}
static KernelTimespec XtimeDeadline(const Xtime& time) {
constexpr auto largest = std::numeric_limits<std::int64_t>::max();
constexpr auto smallest = std::numeric_limits<std::int64_t>::min();
const auto carry = time.nsec / NanosPerSecond;
auto nanos = time.nsec % NanosPerSecond;
auto seconds = time.sec;
if ((carry > 0 && seconds > largest - carry) || (carry < 0 && seconds < smallest - carry)) throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
seconds += carry;
if (nanos < 0) {
if (seconds == smallest) throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
nanos += NanosPerSecond;
--seconds;
}
return {seconds, nanos};
}
extern "C" {
int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t value) {
@ -943,22 +919,6 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void) {
while (sharedPtrSpinLock.test_and_set(std::memory_order_acquire)) std::this_thread::yield();
}
int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) {
if (!target) APS5_INVALID_ARG_EX;
const auto deadline = XtimeDeadline(*target);
for (;;) {
KernelTimespec now{};
if (clock_gettime_nid_postfix(GuestClockRealtime, &now) != 0) throw std::runtime_error("_Thrd_sleep: reading the realtime clock failed");
if (now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) return 0;
KernelTimespec remaining{deadline.tv_sec - now.tv_sec, deadline.tv_nsec - now.tv_nsec};
if (remaining.tv_nsec < 0) {
remaining.tv_nsec += NanosPerSecond;
--remaining.tv_sec;
}
remaining.tv_sec = std::min(remaining.tv_sec, LongestSleepSeconds);
if (nanosleep_nid_postfix(&remaining, nullptr) != 0) throw std::runtime_error("_Thrd_sleep: nanosleep failed");
}
}
int APS5_VABI wcstombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit) {
std::array<std::uint64_t, 16> state{};

View File

@ -190,6 +190,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_libc_threads PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_libc_static_imports_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcStaticImports.cpp)
target_include_directories(guest_libc_static_imports_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_libc_static_imports_tests PRIVATE libc)
configure_windows_unwind(guest_libc_static_imports_tests)
add_test(NAME guest_libc_static_imports COMMAND guest_libc_static_imports_tests)
set_tests_properties(guest_libc_static_imports PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_libc_static_imports PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_fiopen_tests ${LIBS_INCLUDE_DIR}/tests/GuestFiopen.cpp)
target_include_directories(guest_fiopen_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_fiopen_tests PRIVATE libc)

View File

@ -32,4 +32,8 @@ std::uint64_t APS5_VABI libcCyberUnknown08(void) {
return 0;
}
APS5_EXPORT("H2e8t5ScQGc", libc___cxa_finalize);
void APS5_VABI libc___cxa_finalize(void* dsoHandle) {
CxaFinalize_nid_no_patch(dsoHandle);
}
}

View File

@ -68,6 +68,7 @@ int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0
int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; }
int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; }
float APS5_VABI nearbyintf_nid_postfix(float x) { return std::nearbyintf(x); }
double APS5_VABI cbrt_nid_postfix(double x) { return std::cbrt(x); }
double APS5_VABI asin_nid_postfix(double x) {

View File

@ -1,5 +1,8 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <condition_variable>
#include <cstdint>
#include <limits>
@ -9,9 +12,36 @@
#include <string>
#include <system_error>
#include <thread>
#include <time.h>
namespace {
struct Xtime {
std::int64_t sec;
std::int64_t nsec;
};
constexpr std::int64_t nanosPerSecond = 1000000000;
constexpr std::int64_t longestSleepSeconds = 86400;
timespec XtimeDeadline(const Xtime& time) {
constexpr auto largest = std::numeric_limits<std::int64_t>::max();
constexpr auto smallest = std::numeric_limits<std::int64_t>::min();
const auto carry = time.nsec / nanosPerSecond;
auto nanos = time.nsec % nanosPerSecond;
auto seconds = time.sec;
if ((carry > 0 && seconds > largest - carry) || (carry < 0 && seconds < smallest - carry))
throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
seconds += carry;
if (nanos < 0) {
if (seconds == smallest)
throw std::overflow_error("_Thrd_sleep: xtime seconds overflow");
nanos += nanosPerSecond;
--seconds;
}
return {static_cast<time_t>(seconds), static_cast<long>(nanos)};
}
constexpr int threadSuccess = 0;
constexpr int threadNomem = 1;
constexpr int threadBusy = 3;
@ -111,6 +141,24 @@ int APS5_VABI _Mtx_lock_nid_postfix(void** handle) {
return threadSuccess;
}
int APS5_VABI _Mtx_trylock_nid_postfix(void** handle) {
auto* mutex = ResolveMutex(handle, "_Mtx_trylock");
const auto self = std::this_thread::get_id();
if (mutex->owner.load(std::memory_order_acquire) == self) {
if ((mutex->type & mutexRecursive) == 0)
return threadBusy;
if (mutex->count == std::numeric_limits<int>::max())
throw std::overflow_error("_Mtx_trylock: recursive lock count overflow");
++mutex->count;
return threadSuccess;
}
if (!mutex->native.try_lock())
return threadBusy;
mutex->owner.store(self, std::memory_order_release);
mutex->count = 1;
return threadSuccess;
}
int APS5_VABI _Mtx_unlock_nid_postfix(void** handle) {
auto* mutex = ResolveMutex(handle, "_Mtx_unlock");
if (!IsOwnedByCaller(mutex))
@ -178,4 +226,28 @@ int SignalCondition_nid_no_patch(void** handle) {
return threadSuccess;
}
int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime*) {
if (!target)
APS5_INVALID_ARG_EX;
const auto deadline = XtimeDeadline(*target);
for (;;) {
timespec now{};
if (clock_gettime(CLOCK_REALTIME, &now) != 0)
throw std::runtime_error("_Thrd_sleep: reading the realtime clock failed");
if (now.tv_sec > deadline.tv_sec || (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec))
return threadSuccess;
timespec remaining{static_cast<time_t>(deadline.tv_sec - now.tv_sec), static_cast<long>(deadline.tv_nsec - now.tv_nsec)};
if (remaining.tv_nsec < 0) {
remaining.tv_nsec += nanosPerSecond;
--remaining.tv_sec;
}
remaining.tv_sec = std::min<time_t>(remaining.tv_sec, longestSleepSeconds);
if (nanosleep(&remaining, nullptr) != 0) {
if (errno == EINTR)
continue;
throw std::runtime_error("_Thrd_sleep: nanosleep failed");
}
}
}
}

View File

@ -0,0 +1,169 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <atomic>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <stdexcept>
#include <thread>
#include <time.h>
struct Xtime {
std::int64_t sec;
std::int64_t nsec;
};
extern "C" {
float APS5_VABI nearbyintf_nid_postfix(float x);
int APS5_VABI _Mtx_init_nid_postfix(void** handle, int type);
void APS5_VABI _Mtx_destroy_nid_postfix(void** handle);
int APS5_VABI _Mtx_lock_nid_postfix(void** handle);
int APS5_VABI _Mtx_unlock_nid_postfix(void** handle);
int APS5_VABI _Mtx_trylock_nid_postfix(void** handle);
int APS5_VABI _Thrd_sleep_nid_postfix(const Xtime* target, Xtime* remaining);
}
namespace {
void Require(bool value) {
if (!value)
std::abort();
}
template<typename TException, typename TCall>
void RequireThrows(TCall call) {
try {
call();
} catch (const TException&) {
return;
}
std::abort();
}
constexpr int threadSuccess = 0;
constexpr int threadBusy = 3;
constexpr int mutexTryable = 0x02;
constexpr int mutexRecursive = 0x100;
constexpr std::int64_t nanosPerSecond = 1000000000LL;
void TestNearbyintf() {
const float half = nearbyintf_nid_postfix(0.5f);
Require(half == 0.0f);
Require(!std::signbit(half));
const float negHalf = nearbyintf_nid_postfix(-0.5f);
Require(negHalf == 0.0f);
Require(std::signbit(negHalf));
Require(nearbyintf_nid_postfix(1.5f) == 2.0f);
Require(nearbyintf_nid_postfix(-1.5f) == -2.0f);
Require(nearbyintf_nid_postfix(2.5f) == 2.0f);
Require(nearbyintf_nid_postfix(-2.5f) == -2.0f);
const float negZero = nearbyintf_nid_postfix(-0.0f);
Require(negZero == 0.0f);
Require(std::signbit(negZero));
const float inf = std::numeric_limits<float>::infinity();
const float posInf = nearbyintf_nid_postfix(inf);
Require(std::isinf(posInf) && posInf > 0.0f);
const float negInf = nearbyintf_nid_postfix(-inf);
Require(std::isinf(negInf) && negInf < 0.0f);
const float nan = std::numeric_limits<float>::quiet_NaN();
Require(std::isnan(nearbyintf_nid_postfix(nan)));
}
void TestMtxTrylock() {
void* mutex = nullptr;
Require(_Mtx_init_nid_postfix(&mutex, mutexTryable) == threadSuccess && mutex != nullptr);
Require(_Mtx_trylock_nid_postfix(&mutex) == threadSuccess);
Require(_Mtx_trylock_nid_postfix(&mutex) == threadBusy);
Require(_Mtx_unlock_nid_postfix(&mutex) == threadSuccess);
std::atomic<bool> held{false};
std::atomic<bool> release{false};
std::thread other([&] {
Require(_Mtx_lock_nid_postfix(&mutex) == threadSuccess);
held.store(true, std::memory_order_release);
while (!release.load(std::memory_order_acquire))
std::this_thread::yield();
Require(_Mtx_unlock_nid_postfix(&mutex) == threadSuccess);
});
while (!held.load(std::memory_order_acquire))
std::this_thread::yield();
Require(_Mtx_trylock_nid_postfix(&mutex) == threadBusy);
release.store(true, std::memory_order_release);
other.join();
Require(_Mtx_trylock_nid_postfix(&mutex) == threadSuccess);
Require(_Mtx_unlock_nid_postfix(&mutex) == threadSuccess);
_Mtx_destroy_nid_postfix(&mutex);
void* recMutex = nullptr;
Require(_Mtx_init_nid_postfix(&recMutex, mutexTryable | mutexRecursive) == threadSuccess && recMutex != nullptr);
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadSuccess);
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadSuccess);
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadSuccess);
std::thread([&] {
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadBusy);
}).join();
Require(_Mtx_unlock_nid_postfix(&recMutex) == threadSuccess);
std::thread([&] {
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadBusy);
}).join();
Require(_Mtx_unlock_nid_postfix(&recMutex) == threadSuccess);
std::thread([&] {
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadBusy);
}).join();
Require(_Mtx_unlock_nid_postfix(&recMutex) == threadSuccess);
std::thread([&] {
Require(_Mtx_trylock_nid_postfix(&recMutex) == threadSuccess);
Require(_Mtx_unlock_nid_postfix(&recMutex) == threadSuccess);
}).join();
_Mtx_destroy_nid_postfix(&recMutex);
}
void TestThrdSleep() {
timespec before{};
Require(clock_gettime(CLOCK_REALTIME, &before) == 0);
const std::int64_t sleepNanos = 20000000LL;
const std::int64_t targetTotal = static_cast<std::int64_t>(before.tv_sec) * nanosPerSecond + before.tv_nsec + sleepNanos;
const Xtime target{targetTotal / nanosPerSecond, targetTotal % nanosPerSecond};
Require(_Thrd_sleep_nid_postfix(&target, nullptr) == 0);
timespec after{};
Require(clock_gettime(CLOCK_REALTIME, &after) == 0);
const std::int64_t afterTotal = static_cast<std::int64_t>(after.tv_sec) * nanosPerSecond + after.tv_nsec;
Require(afterTotal >= targetTotal);
const Xtime past{before.tv_sec - 1, before.tv_nsec};
Require(_Thrd_sleep_nid_postfix(&past, nullptr) == 0);
RequireThrows<std::invalid_argument>([] {
_Thrd_sleep_nid_postfix(nullptr, nullptr);
});
}
}
int main() {
TestNearbyintf();
TestMtxTrylock();
TestThrdSleep();
return 0;
}

View File

@ -50,6 +50,9 @@ Throughout the project, every function at every stage either **does exactly what
### Unknown function info
- [sceHttpUriBuild](../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) retains `//` for a non-opaque URI independently of the selected fields, following [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/a8a70da595da2a96e0410309472ba4a7eb558144/src/core/libraries/network/http.cpp#L3551-L3558). Black Myth: Wukong builds its `Scheme` header with option 1 and expects `http://` or `https://`. Other partial field combinations follow the PS4 reference and remain unverified on PS5 hardware.
- [_Mtx_trylock](../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `k6pGNMwJB08`) - returns busy for a non-recursive mutex already owned by the caller, assumed to match `_Mtx_lock`'s ownership handling; exact console behavior is unverified.
- [nearbyintf](../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified.
- [_Thrd_sleep](../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, retrying on `EINTR`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched.
- [sceCoredumpGetThreadContextInfo](../../core/libs/prx/libkernel/System/src/Coredump.cpp) (libkernel, NID `ShChva57wIM`) - declared as a throwing stub. The pointer and size arguments and integer return type follow [SharpProspero's PS5 bindings](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelSystem.cs). The context structure and console behavior are unverified; the stub does not read or write caller memory. PPSA01736 imports it
- [sceNpWebApi2PushEventAbortHandle](../../core/libs/prx/libSceNpWebApi2/Export.cpp) (libSceNpWebApi2, NID `1OLgvahaSco`) is a throwing stub. The two signed 32-bit handle arguments and return type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/np/np_web_api2/np_web_api2.cpp) (PS4); the PS5 signature and abort behavior are unverified. PPSA01736 imports it.
- [sceNpGetSandboxInfo](../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager, NID `ZVZ++68nNI8`) - the name hashes to the imported NID (found with nidhunt, confirmed with `tools/nid_names.py`). STAR WARS: Galactic Racer (PPSA28416) imports it; its OnlineSubsystemPS5 calls it with one pointer to a 0x50-byte stack buffer and reads the buffer as a string (discussions/3086). The returned string, the exact parameter struct and console errors are unknown, so every call throws without reading or writing caller memory. A working implementation belongs with that title's bring-up.
@ -369,7 +372,7 @@ Throughout the project, every function at every stage either **does exactly what
- [mkstemp and isatty](../../core/libs/prx/libScePosixForWebKit/Filesystem.cpp) (libScePosixForWebKit) follow FreeBSD's [temporary-file API](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/stdio/mktemp.c) and [terminal check](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/gen/isatty.c), without console verification. `mkstemp` replaces all trailing `X` characters, accepts a fixed name, and creates files through the guest path resolver with exclusive read/write access and mode 0600. Windows file permissions follow the existing kernel file implementation. `isatty` tests Windows console handles rather than all character devices; files, pipes and guest sockets return `ENOTTY`, closed descriptors return `EBADF`.
- [arc4random](../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes from the same thread-local host `std::random_device` as `arc4random_buf`, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent
- [fnmatch](../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) follows FreeBSD 11 [fnmatch.c](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/gen/fnmatch.c) in the C locale: each byte is one character, `FNM_CASEFOLD` folds ASCII letters only, and bracket ranges compare byte values. `[:class:]` names are not character classes: as in FreeBSD 11 through 14, `[[:digit:]]` is the set of `[`, `:`, `d`, `i`, `g`, `t` followed by a literal `]` (classes were added to `fnmatch.c` after 14). Multibyte characters and locale collation are not modelled. A null pattern or string throws
- [Dinkumware thread exports](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follow the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`cthread.c`, `xtime.c`) and its `atomic.c`, [microsoft/STL `cthread.cpp`](https://github.com/microsoft/STL/blob/main/stl/src/cthread.cpp) and C11 Annex K, without console verification. `_Thrd_t` is taken to be the guest `pthread_t`, as the `std::_Pad::_Launch(pthread**)` export suggests: `_Thrd_join` returns `_Thrd_error` (4) when `pthread_join` fails and stores the thread's return value truncated to `int`. `_Thrd_sleep` reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. `_Thrd_join` (32-bit store of the result, 0 or 4) and the spin lock (byte exchange loop, no yield) match that disassembly. The shared_ptr spin lock is one process-wide flag that yields while it spins. `wcstombs_s` is `wcsrtombs_s` with a fresh conversion state, so `*retval` excludes the terminating null as in Annex K, not as in the Microsoft CRT, and `_TRUNCATE` has no meaning
- [Dinkumware thread exports](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follow the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`cthread.c`) and its `atomic.c`, [microsoft/STL `cthread.cpp`](https://github.com/microsoft/STL/blob/main/stl/src/cthread.cpp) and C11 Annex K, without console verification. `_Thrd_t` is taken to be the guest `pthread_t`, as the `std::_Pad::_Launch(pthread**)` export suggests: `_Thrd_join` returns `_Thrd_error` (4) when `pthread_join` fails and stores the thread's return value truncated to `int`. `_Thrd_join` (32-bit store of the result, 0 or 4) and the spin lock (byte exchange loop, no yield) match that disassembly. The shared_ptr spin lock is one process-wide flag that yields while it spins. `wcstombs_s` is `wcsrtombs_s` with a fresh conversion state, so `*retval` excludes the terminating null as in Annex K, not as in the Microsoft CRT, and `_TRUNCATE` has no meaning.
- [_Cnd_signal, _Cnd_init_with_name](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - take a pointer to the handle slot like the libc `_Cnd_*` exports from [#3079](https://github.com/boykopovar/AnyPS5/pull/3079) and act on the same conditions. `_Cnd_signal` wakes one waiter and returns `_Thrd_success` (0), as `_Cnd_broadcast` does; whether the console maps a failing internal signal to `_Thrd_error` is not known. The signature `_Cnd_init_with_name(_Cnd_t*, const char* name)` is inferred from [fpPS4 `ps4_mtx_internal.pas`](https://github.com/red-prig/fpPS4/blob/trunk/src/libcinternal/ps4_mtx_internal.pas), where the PS4 `_Mtx_init` is `_Mtx_init_with_name` with the name `SceLibcI_Mtx` and the name only reaches the pthread object; here it is `_Cnd_init` and the name, which may be null, is ignored. Neither is confirmed on a PS5 `libc.prx`. `_Thrd_id` still throws: microsoft/STL returns an `unsigned int` thread id, but whether the PS5 returns the `pthread_t` or a kernel thread id, and at what width, is not known
- [libSceZlib](../../core/libs/prx/libSceZlib/Export.cpp) completes RFC1950 decompression during submission and supports one outstanding request, null-timeout waits and one result retrieval. Timed waits, multiple outstanding requests, repeated retrieval and finalization with an unread result throw. The 64-bit initialization length and 64 KiB output limit with 2 KiB destination alignment follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Compression/Zlib.cs); the PS4 initialization length is 32 bits. shadPS4 also requires the output length to be a multiple of 2 KiB and returns `0x81120016` for invalid arguments; this implementation accepts any length up to 64 KiB (SharpProspero documents only the pointer alignment) and throws on invalid arguments. Scratch memory is not used, and its firmware size/alignment requirements are unknown. The initialization errors follow SharpProspero; malformed/checksum/truncated streams report `0x811200FF` and insufficient output `0x8112001C` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/zlib/zlib.cpp). PS5 error precedence, scheduling, completion selection, result consumption and cancellation are unverified. The payload SDK's FreeBSD kernel `z_stream` and host errno are not this library's ABI.
- [Windows file timestamps](../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes` and `futimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond