fix(libc): add missing Unity engine static imports
This commit is contained in:
parent
bb48d60fc6
commit
b8a1cbe42b
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue